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