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 | /* |
6 | * GCSCAN.CPP |
7 | * |
8 | * GC Root Scanning |
9 | * |
10 | |
11 | * |
12 | */ |
13 | |
14 | #include "common.h" |
15 | |
16 | #include "gcenv.h" |
17 | |
18 | #include "gcscan.h" |
19 | #include "gc.h" |
20 | #include "objecthandle.h" |
21 | |
22 | VOLATILE(int32_t) GCScan::m_GcStructuresInvalidCnt = 1; |
23 | |
24 | bool GCScan::GetGcRuntimeStructuresValid () |
25 | { |
26 | LIMITED_METHOD_CONTRACT; |
27 | SUPPORTS_DAC; |
28 | _ASSERTE ((int32_t)m_GcStructuresInvalidCnt >= 0); |
29 | return (int32_t)m_GcStructuresInvalidCnt == 0; |
30 | } |
31 | |
32 | #ifndef DACCESS_COMPILE |
33 | |
34 | // |
35 | // Dependent handle promotion scan support |
36 | // |
37 | |
38 | // This method is called first during the mark phase. It's job is to set up the context for further scanning |
39 | // (remembering the scan parameters the GC gives us and initializing some state variables we use to determine |
40 | // whether further scans will be required or not). |
41 | // |
42 | // This scan is not guaranteed to return complete results due to the GC context in which we are called. In |
43 | // particular it is possible, due to either a mark stack overflow or unsynchronized operation in server GC |
44 | // mode, that not all reachable objects will be reported as promoted yet. However, the operations we perform |
45 | // will still be correct and this scan allows us to spot a common optimization where no dependent handles are |
46 | // due for retirement in this particular GC. This is an important optimization to take advantage of since |
47 | // synchronizing the GC to calculate complete results is a costly operation. |
48 | void GCScan::GcDhInitialScan(promote_func* fn, int condemned, int max_gen, ScanContext* sc) |
49 | { |
50 | // We allocate space for dependent handle scanning context during Ref_Initialize. Under server GC there |
51 | // are actually as many contexts as heaps (and CPUs). Ref_GetDependentHandleContext() retrieves the |
52 | // correct context for the current GC thread based on the ScanContext passed to us by the GC. |
53 | DhContext *pDhContext = Ref_GetDependentHandleContext(sc); |
54 | |
55 | // Record GC callback parameters in the DH context so that the GC doesn't continually have to pass the |
56 | // same data to each call. |
57 | pDhContext->m_pfnPromoteFunction = fn; |
58 | pDhContext->m_iCondemned = condemned; |
59 | pDhContext->m_iMaxGen = max_gen; |
60 | pDhContext->m_pScanContext = sc; |
61 | |
62 | // Look for dependent handle whose primary has been promoted but whose secondary has not. Promote the |
63 | // secondary in those cases. Additionally this scan sets the m_fUnpromotedPrimaries and m_fPromoted state |
64 | // flags in the DH context. The m_fUnpromotedPrimaries flag is the most interesting here: if this flag is |
65 | // false after the scan then it doesn't matter how many object promotions might currently be missing since |
66 | // there are no secondary objects that are currently unpromoted anyway. This is the (hopefully common) |
67 | // circumstance under which we don't have to perform any costly additional re-scans. |
68 | Ref_ScanDependentHandlesForPromotion(pDhContext); |
69 | } |
70 | |
71 | // This method is called after GcDhInitialScan and before each subsequent scan (GcDhReScan below). It |
72 | // determines whether any handles are left that have unpromoted secondaries. |
73 | bool GCScan::GcDhUnpromotedHandlesExist(ScanContext* sc) |
74 | { |
75 | WRAPPER_NO_CONTRACT; |
76 | // Locate our dependent handle context based on the GC context. |
77 | DhContext *pDhContext = Ref_GetDependentHandleContext(sc); |
78 | |
79 | return pDhContext->m_fUnpromotedPrimaries; |
80 | } |
81 | |
82 | // Perform a re-scan of dependent handles, promoting secondaries associated with newly promoted primaries as |
83 | // above. We may still need to call this multiple times since promotion of a secondary late in the table could |
84 | // promote a primary earlier in the table. Also, GC graph promotions are not guaranteed to be complete by the |
85 | // time the promotion callback returns (the mark stack can overflow). As a result the GC might have to call |
86 | // this method in a loop. The scan records state that let's us know when to terminate (no further handles to |
87 | // be promoted or no promotions in the last scan). Returns true if at least one object was promoted as a |
88 | // result of the scan. |
89 | bool GCScan::GcDhReScan(ScanContext* sc) |
90 | { |
91 | // Locate our dependent handle context based on the GC context. |
92 | DhContext *pDhContext = Ref_GetDependentHandleContext(sc); |
93 | |
94 | return Ref_ScanDependentHandlesForPromotion(pDhContext); |
95 | } |
96 | |
97 | /* |
98 | * Scan for dead weak pointers |
99 | */ |
100 | |
101 | void GCScan::GcWeakPtrScan( promote_func* fn, int condemned, int max_gen, ScanContext* sc ) |
102 | { |
103 | // Clear out weak pointers that are no longer live. |
104 | Ref_CheckReachable(condemned, max_gen, (uintptr_t)sc); |
105 | |
106 | // Clear any secondary objects whose primary object is now definitely dead. |
107 | Ref_ScanDependentHandlesForClearing(condemned, max_gen, sc, fn); |
108 | } |
109 | |
110 | static void CALLBACK CheckPromoted(_UNCHECKED_OBJECTREF *pObjRef, uintptr_t * /*pExtraInfo*/, uintptr_t /*lp1*/, uintptr_t /*lp2*/) |
111 | { |
112 | LIMITED_METHOD_CONTRACT; |
113 | |
114 | LOG((LF_GC, LL_INFO100000, LOG_HANDLE_OBJECT_CLASS("Checking referent of Weak-" , pObjRef, "to " , *pObjRef))); |
115 | |
116 | Object **pRef = (Object **)pObjRef; |
117 | if (!g_theGCHeap->IsPromoted(*pRef)) |
118 | { |
119 | LOG((LF_GC, LL_INFO100, LOG_HANDLE_OBJECT_CLASS("Severing Weak-" , pObjRef, "to unreachable " , *pObjRef))); |
120 | |
121 | *pRef = NULL; |
122 | } |
123 | else |
124 | { |
125 | LOG((LF_GC, LL_INFO1000000, "reachable " LOG_OBJECT_CLASS(*pObjRef))); |
126 | } |
127 | } |
128 | |
129 | void GCScan::GcWeakPtrScanBySingleThread( int condemned, int max_gen, ScanContext* sc ) |
130 | { |
131 | UNREFERENCED_PARAMETER(condemned); |
132 | UNREFERENCED_PARAMETER(max_gen); |
133 | GCToEEInterface::SyncBlockCacheWeakPtrScan(&CheckPromoted, (uintptr_t)sc, 0); |
134 | } |
135 | |
136 | void GCScan::GcScanSizedRefs(promote_func* fn, int condemned, int max_gen, ScanContext* sc) |
137 | { |
138 | Ref_ScanSizedRefHandles(condemned, max_gen, sc, fn); |
139 | } |
140 | |
141 | void GCScan::GcShortWeakPtrScan(promote_func* fn, int condemned, int max_gen, |
142 | ScanContext* sc) |
143 | { |
144 | UNREFERENCED_PARAMETER(fn); |
145 | Ref_CheckAlive(condemned, max_gen, (uintptr_t)sc); |
146 | } |
147 | |
148 | /* |
149 | * Scan all stack roots in this 'namespace' |
150 | */ |
151 | |
152 | void GCScan::GcScanRoots(promote_func* fn, int condemned, int max_gen, |
153 | ScanContext* sc) |
154 | { |
155 | GCToEEInterface::GcScanRoots(fn, condemned, max_gen, sc); |
156 | } |
157 | |
158 | /* |
159 | * Scan all handle roots in this 'namespace' |
160 | */ |
161 | |
162 | |
163 | void GCScan::GcScanHandles (promote_func* fn, int condemned, int max_gen, |
164 | ScanContext* sc) |
165 | { |
166 | STRESS_LOG1(LF_GC|LF_GCROOTS, LL_INFO10, "GcScanHandles (Promotion Phase = %d)\n" , sc->promotion); |
167 | if (sc->promotion) |
168 | { |
169 | Ref_TracePinningRoots(condemned, max_gen, sc, fn); |
170 | Ref_TraceNormalRoots(condemned, max_gen, sc, fn); |
171 | } |
172 | else |
173 | { |
174 | Ref_UpdatePointers(condemned, max_gen, sc, fn); |
175 | Ref_UpdatePinnedPointers(condemned, max_gen, sc, fn); |
176 | Ref_ScanDependentHandlesForRelocation(condemned, max_gen, sc, fn); |
177 | } |
178 | } |
179 | |
180 | /* |
181 | * Scan all handle roots in this 'namespace' for profiling |
182 | */ |
183 | |
184 | void GCScan::GcScanHandlesForProfilerAndETW (int max_gen, ScanContext* sc, handle_scan_fn fn) |
185 | { |
186 | LIMITED_METHOD_CONTRACT; |
187 | |
188 | #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) |
189 | LOG((LF_GC|LF_GCROOTS, LL_INFO10, "Profiler Root Scan Phase, Handles\n" )); |
190 | Ref_ScanHandlesForProfilerAndETW(max_gen, (uintptr_t)sc, fn); |
191 | #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) |
192 | } |
193 | |
194 | /* |
195 | * Scan dependent handles in this 'namespace' for profiling |
196 | */ |
197 | void GCScan::GcScanDependentHandlesForProfilerAndETW (int max_gen, ScanContext* sc, handle_scan_fn fn) |
198 | { |
199 | LIMITED_METHOD_CONTRACT; |
200 | |
201 | #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) |
202 | LOG((LF_GC|LF_GCROOTS, LL_INFO10, "Profiler Root Scan Phase, DependentHandles\n" )); |
203 | Ref_ScanDependentHandlesForProfilerAndETW(max_gen, sc, fn); |
204 | #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) |
205 | } |
206 | |
207 | void GCScan::GcRuntimeStructuresValid (BOOL bValid) |
208 | { |
209 | WRAPPER_NO_CONTRACT; |
210 | if (!bValid) |
211 | { |
212 | int32_t result; |
213 | result = Interlocked::Increment (&m_GcStructuresInvalidCnt); |
214 | _ASSERTE (result > 0); |
215 | } |
216 | else |
217 | { |
218 | int32_t result; |
219 | result = Interlocked::Decrement (&m_GcStructuresInvalidCnt); |
220 | _ASSERTE (result >= 0); |
221 | } |
222 | } |
223 | |
224 | void GCScan::GcDemote (int condemned, int max_gen, ScanContext* sc) |
225 | { |
226 | Ref_RejuvenateHandles (condemned, max_gen, (uintptr_t)sc); |
227 | if (!IsServerHeap() || sc->thread_number == 0) |
228 | GCToEEInterface::SyncBlockCacheDemote(max_gen); |
229 | } |
230 | |
231 | void GCScan::GcPromotionsGranted (int condemned, int max_gen, ScanContext* sc) |
232 | { |
233 | Ref_AgeHandles(condemned, max_gen, (uintptr_t)sc); |
234 | if (!IsServerHeap() || sc->thread_number == 0) |
235 | GCToEEInterface::SyncBlockCachePromotionsGranted(max_gen); |
236 | } |
237 | |
238 | |
239 | size_t GCScan::AskForMoreReservedMemory (size_t old_size, size_t need_size) |
240 | { |
241 | LIMITED_METHOD_CONTRACT; |
242 | |
243 | #if !defined(FEATURE_CORECLR) && !defined(FEATURE_REDHAWK) |
244 | // call the host.... |
245 | |
246 | IGCHostControl *pGCHostControl = CorHost::GetGCHostControl(); |
247 | |
248 | if (pGCHostControl) |
249 | { |
250 | size_t new_max_limit_size = need_size; |
251 | pGCHostControl->RequestVirtualMemLimit (old_size, |
252 | (SIZE_T*)&new_max_limit_size); |
253 | return new_max_limit_size; |
254 | } |
255 | #endif |
256 | |
257 | return old_size + need_size; |
258 | } |
259 | |
260 | void GCScan::VerifyHandleTable(int condemned, int max_gen, ScanContext* sc) |
261 | { |
262 | LIMITED_METHOD_CONTRACT; |
263 | Ref_VerifyHandleTable(condemned, max_gen, sc); |
264 | } |
265 | |
266 | #endif // !DACCESS_COMPILE |
267 | |