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.
11GPTR_DECL(IGCHeap, g_pGCHeap);
12
13#ifndef DACCESS_COMPILE
14extern "C" {
15#endif // !DACCESS_COMPILE
16GPTR_DECL(uint8_t,g_lowest_address);
17GPTR_DECL(uint8_t,g_highest_address);
18GPTR_DECL(uint32_t,g_card_table);
19GVAL_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.
28extern "C" gc_alloc_context g_global_alloc_context;
29
30extern "C" uint32_t* g_card_bundle_table;
31extern "C" uint8_t* g_ephemeral_low;
32extern "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.
38extern "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.
41extern "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.
47extern 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.
58typedef DPTR(GcDacVars) PTR_GcDacVars;
59GPTR_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.
64class GCHeapUtilities {
65public:
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
213private:
214 // This class should never be instantiated.
215 GCHeapUtilities() = delete;
216};
217
218#endif // _GCHEAPUTILITIES_H_
219
220