1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | #ifndef _GCHEAPUTILITIES_H_ |
6 | #define _GCHEAPUTILITIES_H_ |
7 | |
8 | #include "gcinterface.h" |
9 | |
10 | // The singular heap instance. |
11 | GPTR_DECL(IGCHeap, g_pGCHeap); |
12 | |
13 | #ifndef DACCESS_COMPILE |
14 | extern "C" { |
15 | #endif // !DACCESS_COMPILE |
16 | GPTR_DECL(uint8_t,g_lowest_address); |
17 | GPTR_DECL(uint8_t,g_highest_address); |
18 | GPTR_DECL(uint32_t,g_card_table); |
19 | GVAL_DECL(GCHeapType, g_heap_type); |
20 | #ifndef DACCESS_COMPILE |
21 | } |
22 | #endif // !DACCESS_COMPILE |
23 | |
24 | // For single-proc machines, the EE will use a single, shared alloc context |
25 | // for all allocations. In order to avoid extra indirections in assembly |
26 | // allocation helpers, the EE owns the global allocation context and the |
27 | // GC will update it when it needs to. |
28 | extern "C" gc_alloc_context g_global_alloc_context; |
29 | |
30 | extern "C" uint32_t* g_card_bundle_table; |
31 | extern "C" uint8_t* g_ephemeral_low; |
32 | extern "C" uint8_t* g_ephemeral_high; |
33 | |
34 | #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP |
35 | |
36 | // Table containing the dirty state. This table is translated to exclude the lowest address it represents, see |
37 | // TranslateTableToExcludeHeapStartAddress. |
38 | extern "C" uint8_t *g_sw_ww_table; |
39 | |
40 | // Write watch may be disabled when it is not needed (between GCs for instance). This indicates whether it is enabled. |
41 | extern "C" bool g_sw_ww_enabled_for_gc_heap; |
42 | |
43 | #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP |
44 | |
45 | // g_gc_dac_vars is a structure of pointers to GC globals that the |
46 | // DAC uses. It is not exposed directly to the DAC. |
47 | extern GcDacVars g_gc_dac_vars; |
48 | |
49 | // Instead of exposing g_gc_dac_vars to the DAC, a pointer to it |
50 | // is exposed here (g_gcDacGlobals). The reason for this is to avoid |
51 | // a problem in which a debugger attaches to a program while the program |
52 | // is in the middle of initializing the GC DAC vars - if the "publishing" |
53 | // of DAC vars isn't atomic, the debugger could see a partially initialized |
54 | // GcDacVars structure. |
55 | // |
56 | // Instead, the debuggee "publishes" GcDacVars by assigning a pointer to g_gc_dac_vars |
57 | // to this global, and the DAC will read this global. |
58 | typedef DPTR(GcDacVars) PTR_GcDacVars; |
59 | GPTR_DECL(GcDacVars, g_gcDacGlobals); |
60 | |
61 | // GCHeapUtilities provides a number of static methods |
62 | // that operate on the global heap instance. It can't be |
63 | // instantiated. |
64 | class GCHeapUtilities { |
65 | public: |
66 | // Retrieves the GC heap. |
67 | inline static IGCHeap* GetGCHeap() |
68 | { |
69 | LIMITED_METHOD_CONTRACT; |
70 | |
71 | assert(g_pGCHeap != nullptr); |
72 | return g_pGCHeap; |
73 | } |
74 | |
75 | // Returns true if the heap has been initialized, false otherwise. |
76 | inline static bool IsGCHeapInitialized() |
77 | { |
78 | LIMITED_METHOD_CONTRACT; |
79 | |
80 | return g_pGCHeap != nullptr; |
81 | } |
82 | |
83 | // Returns true if a the heap is initialized and a garbage collection |
84 | // is in progress, false otherwise. |
85 | inline static bool IsGCInProgress(bool bConsiderGCStart = false) |
86 | { |
87 | WRAPPER_NO_CONTRACT; |
88 | |
89 | return (IsGCHeapInitialized() ? GetGCHeap()->IsGCInProgressHelper(bConsiderGCStart) : false); |
90 | } |
91 | |
92 | // Returns true if we should be competing marking for statics. This |
93 | // influences the behavior of `GCToEEInterface::GcScanRoots`. |
94 | inline static bool MarkShouldCompeteForStatics() |
95 | { |
96 | WRAPPER_NO_CONTRACT; |
97 | |
98 | return IsServerHeap() && g_SystemInfo.dwNumberOfProcessors >= 2; |
99 | } |
100 | |
101 | // Waits until a GC is complete, if the heap has been initialized. |
102 | inline static void WaitForGCCompletion(bool bConsiderGCStart = false) |
103 | { |
104 | WRAPPER_NO_CONTRACT; |
105 | |
106 | if (IsGCHeapInitialized()) |
107 | GetGCHeap()->WaitUntilGCComplete(bConsiderGCStart); |
108 | } |
109 | |
110 | // Returns true if the held GC heap is a Server GC heap, false otherwise. |
111 | inline static bool IsServerHeap() |
112 | { |
113 | LIMITED_METHOD_CONTRACT; |
114 | |
115 | #ifdef FEATURE_SVR_GC |
116 | _ASSERTE(g_heap_type != GC_HEAP_INVALID); |
117 | return g_heap_type == GC_HEAP_SVR; |
118 | #else |
119 | return false; |
120 | #endif // FEATURE_SVR_GC |
121 | } |
122 | |
123 | static bool UseThreadAllocationContexts() |
124 | { |
125 | // When running on a single-proc system, it's more efficient to use a single global |
126 | // allocation context for SOH allocations than to use one for every thread. |
127 | #if defined(_TARGET_ARM_) || defined(FEATURE_PAL) || defined(FEATURE_REDHAWK) |
128 | return true; |
129 | #else |
130 | return IsServerHeap() || ::GetCurrentProcessCpuCount() != 1; |
131 | #endif |
132 | |
133 | } |
134 | |
135 | #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP |
136 | |
137 | // Returns True if software write watch is currently enabled for the GC Heap, |
138 | // or False if it is not. |
139 | inline static bool SoftwareWriteWatchIsEnabled() |
140 | { |
141 | WRAPPER_NO_CONTRACT; |
142 | |
143 | return g_sw_ww_enabled_for_gc_heap; |
144 | } |
145 | |
146 | // In accordance with the SoftwareWriteWatch scheme, marks a given address as |
147 | // "dirty" (e.g. has been written to). |
148 | inline static void SoftwareWriteWatchSetDirty(void* address, size_t write_size) |
149 | { |
150 | LIMITED_METHOD_CONTRACT; |
151 | |
152 | // We presumably have just written something to this address, so it can't be null. |
153 | assert(address != nullptr); |
154 | |
155 | // The implementation is limited to writes of a pointer size or less. Writes larger |
156 | // than pointer size may cross page boundaries and would require us to potentially |
157 | // set more than one entry in the SWW table, which can't be done atomically under |
158 | // the current scheme. |
159 | assert(write_size <= sizeof(void*)); |
160 | |
161 | size_t table_byte_index = reinterpret_cast<size_t>(address) >> SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift; |
162 | |
163 | // The table byte index that we calculate for the address should be the same as the one |
164 | // calculated for a pointer to the end of the written region. If this were not the case, |
165 | // this write crossed a boundary and would dirty two pages. |
166 | #ifdef _DEBUG |
167 | uint8_t* end_of_write_ptr = reinterpret_cast<uint8_t*>(address) + (write_size - 1); |
168 | assert(table_byte_index == reinterpret_cast<size_t>(end_of_write_ptr) >> SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift); |
169 | #endif |
170 | uint8_t* table_address = &g_sw_ww_table[table_byte_index]; |
171 | if (*table_address == 0) |
172 | { |
173 | *table_address = 0xFF; |
174 | } |
175 | } |
176 | |
177 | // In accordance with the SoftwareWriteWatch scheme, marks a range of addresses |
178 | // as dirty, starting at the given address and with the given length. |
179 | inline static void SoftwareWriteWatchSetDirtyRegion(void* address, size_t length) |
180 | { |
181 | LIMITED_METHOD_CONTRACT; |
182 | |
183 | // We presumably have just memcopied something to this address, so it can't be null. |
184 | assert(address != nullptr); |
185 | |
186 | // The "base index" is the first index in the SWW table that covers the target |
187 | // region of memory. |
188 | size_t base_index = reinterpret_cast<size_t>(address) >> SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift; |
189 | |
190 | // The "end_index" is the last index in the SWW table that covers the target |
191 | // region of memory. |
192 | uint8_t* end_pointer = reinterpret_cast<uint8_t*>(address) + length - 1; |
193 | size_t end_index = reinterpret_cast<size_t>(end_pointer) >> SOFTWARE_WRITE_WATCH_AddressToTableByteIndexShift; |
194 | |
195 | // We'll mark the entire region of memory as dirty by memseting all entries in |
196 | // the SWW table between the start and end indexes. |
197 | memset(&g_sw_ww_table[base_index], ~0, end_index - base_index + 1); |
198 | } |
199 | #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP |
200 | |
201 | #ifndef DACCESS_COMPILE |
202 | // Gets the module that contains the GC. |
203 | static HMODULE GetGCModule(); |
204 | |
205 | // Loads (if using a standalone GC) and initializes the GC. |
206 | static HRESULT LoadAndInitialize(); |
207 | |
208 | // Records a change in eventing state. This ultimately will inform the GC that it needs to be aware |
209 | // of new events being enabled. |
210 | static void RecordEventStateChange(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level); |
211 | #endif // DACCESS_COMPILE |
212 | |
213 | private: |
214 | // This class should never be instantiated. |
215 | GCHeapUtilities() = delete; |
216 | }; |
217 | |
218 | #endif // _GCHEAPUTILITIES_H_ |
219 | |
220 | |