| 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 | |