// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // /* * Wraps handle table to implement various handle types (Strong, Weak, etc.) * * */ #include "common.h" #include "gcenv.h" #include "gc.h" #include "gcscan.h" #include "objecthandle.h" #include "handletablepriv.h" #ifdef FEATURE_COMINTEROP #include "comcallablewrapper.h" #endif // FEATURE_COMINTEROP #ifndef FEATURE_REDHAWK #include "nativeoverlapped.h" #endif // FEATURE_REDHAWK GVAL_IMPL(HandleTableMap, g_HandleTableMap); // Array of contexts used while scanning dependent handles for promotion. There are as many contexts as GC // heaps and they're allocated by Ref_Initialize and initialized during each GC by GcDhInitialScan. DhContext *g_pDependentHandleContexts; #ifndef DACCESS_COMPILE //---------------------------------------------------------------------------- /* * struct VARSCANINFO * * used when tracing variable-strength handles. */ struct VARSCANINFO { LPARAM lEnableMask; // mask of types to trace HANDLESCANPROC pfnTrace; // tracing function to use LPARAM lp2; // second parameter }; //---------------------------------------------------------------------------- /* * Scan callback for tracing variable-strength handles. * * This callback is called to trace individual objects referred to by handles * in the variable-strength table. */ void CALLBACK VariableTraceDispatcher(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { WRAPPER_NO_CONTRACT; // lp2 is a pointer to our VARSCANINFO struct VARSCANINFO *pInfo = (struct VARSCANINFO *)lp2; // is the handle's dynamic type one we're currently scanning? if ((*pExtraInfo & pInfo->lEnableMask) != 0) { // yes - call the tracing function for this handle pInfo->pfnTrace(pObjRef, NULL, lp1, pInfo->lp2); } } #ifdef FEATURE_COMINTEROP /* * Scan callback for tracing ref-counted handles. * * This callback is called to trace individual objects referred to by handles * in the refcounted table. */ void CALLBACK PromoteRefCounted(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { WRAPPER_NO_CONTRACT; // there are too many races when asychnronously scanning ref-counted handles so we no longer support it _ASSERTE(!((ScanContext*)lp1)->concurrent); LOG((LF_GC, LL_INFO1000, LOG_HANDLE_OBJECT_CLASS("", pObjRef, "causes promotion of ", *pObjRef))); Object *pObj = VolatileLoad((PTR_Object*)pObjRef); #ifdef _DEBUG Object *pOldObj = pObj; #endif if (!HndIsNullOrDestroyedHandle(pObj) && !GCHeap::GetGCHeap()->IsPromoted(pObj)) { //@todo optimize the access to the ref-count ComCallWrapper* pWrap = ComCallWrapper::GetWrapperForObject((OBJECTREF)pObj); _ASSERTE(pWrap != NULL); BOOL fIsActive = pWrap->IsWrapperActive(); if (fIsActive) { _ASSERTE(lp2); promote_func* callback = (promote_func*) lp2; callback(&pObj, (ScanContext *)lp1, 0); } } // Assert this object wasn't relocated since we are passing a temporary object's address. _ASSERTE(pOldObj == pObj); } #endif // FEATURE_COMINTEROP void CALLBACK TraceDependentHandle(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { WRAPPER_NO_CONTRACT; if (pObjRef == NULL || pExtraInfo == NULL) return; // At this point, it's possible that either or both of the primary and secondary // objects are NULL. However, if the secondary object is non-NULL, then the primary // object should also be non-NULL. _ASSERTE(*pExtraInfo == NULL || *pObjRef != NULL); // lp2 is a HANDLESCANPROC HANDLESCANPROC pfnTrace = (HANDLESCANPROC) lp2; // is the handle's secondary object non-NULL? if ((*pObjRef != NULL) && (*pExtraInfo != 0)) { // yes - call the tracing function for this handle pfnTrace(pObjRef, NULL, lp1, *pExtraInfo); } } void CALLBACK UpdateDependentHandle(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { LIMITED_METHOD_CONTRACT; _ASSERTE(pExtraInfo); Object **pPrimaryRef = (Object **)pObjRef; Object **pSecondaryRef = (Object **)pExtraInfo; LOG((LF_GC|LF_ENC, LL_INFO10000, LOG_HANDLE_OBJECT("Querying for new location of ", pPrimaryRef, "to ", *pPrimaryRef))); LOG((LF_GC|LF_ENC, LL_INFO10000, LOG_HANDLE_OBJECT(" and ", pSecondaryRef, "to ", *pSecondaryRef))); #ifdef _DEBUG Object *pOldPrimary = *pPrimaryRef; Object *pOldSecondary = *pSecondaryRef; #endif _ASSERTE(lp2); promote_func* callback = (promote_func*) lp2; callback(pPrimaryRef, (ScanContext *)lp1, 0); callback(pSecondaryRef, (ScanContext *)lp1, 0); #ifdef _DEBUG if (pOldPrimary != *pPrimaryRef) LOG((LF_GC|LF_ENC, LL_INFO10000, "Updating " FMT_HANDLE "from" FMT_ADDR "to " FMT_OBJECT "\n", DBG_ADDR(pPrimaryRef), DBG_ADDR(pOldPrimary), DBG_ADDR(*pPrimaryRef))); else LOG((LF_GC|LF_ENC, LL_INFO10000, "Updating " FMT_HANDLE "- " FMT_OBJECT "did not move\n", DBG_ADDR(pPrimaryRef), DBG_ADDR(*pPrimaryRef))); if (pOldSecondary != *pSecondaryRef) LOG((LF_GC|LF_ENC, LL_INFO10000, "Updating " FMT_HANDLE "from" FMT_ADDR "to " FMT_OBJECT "\n", DBG_ADDR(pSecondaryRef), DBG_ADDR(pOldSecondary), DBG_ADDR(*pSecondaryRef))); else LOG((LF_GC|LF_ENC, LL_INFO10000, "Updating " FMT_HANDLE "- " FMT_OBJECT "did not move\n", DBG_ADDR(pSecondaryRef), DBG_ADDR(*pSecondaryRef))); #endif } void CALLBACK PromoteDependentHandle(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { LIMITED_METHOD_CONTRACT; _ASSERTE(pExtraInfo); Object **pPrimaryRef = (Object **)pObjRef; Object **pSecondaryRef = (Object **)pExtraInfo; LOG((LF_GC|LF_ENC, LL_INFO1000, "Checking promotion of DependentHandle")); LOG((LF_GC|LF_ENC, LL_INFO1000, LOG_HANDLE_OBJECT_CLASS("\tPrimary:\t", pObjRef, "to ", *pObjRef))); LOG((LF_GC|LF_ENC, LL_INFO1000, LOG_HANDLE_OBJECT_CLASS("\tSecondary\t", pSecondaryRef, "to ", *pSecondaryRef))); ScanContext *sc = (ScanContext*)lp1; DhContext *pDhContext = Ref_GetDependentHandleContext(sc); if (*pObjRef && GCHeap::GetGCHeap()->IsPromoted(*pPrimaryRef)) { if (!GCHeap::GetGCHeap()->IsPromoted(*pSecondaryRef)) { LOG((LF_GC|LF_ENC, LL_INFO10000, "\tPromoting secondary " LOG_OBJECT_CLASS(*pSecondaryRef))); _ASSERTE(lp2); promote_func* callback = (promote_func*) lp2; callback(pSecondaryRef, (ScanContext *)lp1, 0); // need to rescan because we might have promoted an object that itself has added fields and this // promotion might be all that is pinning that object. If we've already scanned that dependent // handle relationship, we could lose it secondary object. pDhContext->m_fPromoted = true; } } else if (*pObjRef) { // If we see a non-cleared primary which hasn't been promoted, record the fact. We will only require a // rescan if this flag has been set (if it's clear then the previous scan found only clear and // promoted handles, so there's no chance of finding an additional handle being promoted on a // subsequent scan). pDhContext->m_fUnpromotedPrimaries = true; } } void CALLBACK ClearDependentHandle(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { LIMITED_METHOD_CONTRACT; _ASSERTE(pExtraInfo); Object **pPrimaryRef = (Object **)pObjRef; Object **pSecondaryRef = (Object **)pExtraInfo; LOG((LF_GC|LF_ENC, LL_INFO1000, "Checking referent of DependentHandle")); LOG((LF_GC|LF_ENC, LL_INFO1000, LOG_HANDLE_OBJECT_CLASS("\tPrimary:\t", pPrimaryRef, "to ", *pPrimaryRef))); LOG((LF_GC|LF_ENC, LL_INFO1000, LOG_HANDLE_OBJECT_CLASS("\tSecondary\t", pSecondaryRef, "to ", *pSecondaryRef))); if (!GCHeap::GetGCHeap()->IsPromoted(*pPrimaryRef)) { LOG((LF_GC|LF_ENC, LL_INFO1000, "\tunreachable ", LOG_OBJECT_CLASS(*pPrimaryRef))); LOG((LF_GC|LF_ENC, LL_INFO1000, "\tunreachable ", LOG_OBJECT_CLASS(*pSecondaryRef))); *pPrimaryRef = NULL; *pSecondaryRef = NULL; } else { _ASSERTE(GCHeap::GetGCHeap()->IsPromoted(*pSecondaryRef)); LOG((LF_GC|LF_ENC, LL_INFO10000, "\tPrimary is reachable " LOG_OBJECT_CLASS(*pPrimaryRef))); LOG((LF_GC|LF_ENC, LL_INFO10000, "\tSecondary is reachable " LOG_OBJECT_CLASS(*pSecondaryRef))); } } /* * Scan callback for pinning handles. * * This callback is called to pin individual objects referred to by handles in * the pinning table. */ void CALLBACK PinObject(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_SO_TOLERANT; STATIC_CONTRACT_MODE_COOPERATIVE; // PINNING IS BAD - DON'T DO IT IF YOU CAN AVOID IT LOG((LF_GC, LL_WARNING, LOG_HANDLE_OBJECT_CLASS("WARNING: ", pObjRef, "causes pinning of ", *pObjRef))); Object **pRef = (Object **)pObjRef; _ASSERTE(lp2); promote_func* callback = (promote_func*) lp2; callback(pRef, (ScanContext *)lp1, GC_CALL_PINNED); #ifndef FEATURE_REDHAWK Object * pPinnedObj = *pRef; if (!HndIsNullOrDestroyedHandle(pPinnedObj) && pPinnedObj->GetGCSafeMethodTable() == g_pOverlappedDataClass) { // reporting the pinned user objects OverlappedDataObject *pOverlapped = (OverlappedDataObject *)pPinnedObj; if (pOverlapped->m_userObject != NULL) { //callback(OBJECTREF_TO_UNCHECKED_OBJECTREF(pOverlapped->m_userObject), (ScanContext *)lp1, GC_CALL_PINNED); if (pOverlapped->m_isArray) { pOverlapped->m_userObjectInternal = static_cast(OBJECTREFToObject(pOverlapped->m_userObject)); ArrayBase* pUserObject = (ArrayBase*)OBJECTREFToObject(pOverlapped->m_userObject); Object **ppObj = (Object**)pUserObject->GetDataPtr(TRUE); SIZE_T num = pUserObject->GetNumComponents(); for (SIZE_T i = 0; i < num; i ++) { callback(ppObj + i, (ScanContext *)lp1, GC_CALL_PINNED); } } else { callback(&OBJECTREF_TO_UNCHECKED_OBJECTREF(pOverlapped->m_userObject), (ScanContext *)lp1, GC_CALL_PINNED); } } if (pOverlapped->GetAppDomainId() != DefaultADID && pOverlapped->GetAppDomainIndex().m_dwIndex == DefaultADID) { OverlappedDataObject::MarkCleanupNeededFromGC(); } } #endif // !FEATURE_REDHAWK } /* * Scan callback for tracing strong handles. * * This callback is called to trace individual objects referred to by handles * in the strong table. */ void CALLBACK PromoteObject(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO1000, LOG_HANDLE_OBJECT_CLASS("", pObjRef, "causes promotion of ", *pObjRef))); Object **ppRef = (Object **)pObjRef; _ASSERTE(lp2); promote_func* callback = (promote_func*) lp2; callback(ppRef, (ScanContext *)lp1, 0); } /* * Scan callback for disconnecting dead handles. * * This callback is called to check promotion of individual objects referred to by * handles in the weak tables. */ void CALLBACK CheckPromoted(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO100000, LOG_HANDLE_OBJECT_CLASS("Checking referent of Weak-", pObjRef, "to ", *pObjRef))); Object **ppRef = (Object **)pObjRef; if (!GCHeap::GetGCHeap()->IsPromoted(*ppRef)) { LOG((LF_GC, LL_INFO100, LOG_HANDLE_OBJECT_CLASS("Severing Weak-", pObjRef, "to unreachable ", *pObjRef))); *ppRef = NULL; } else { LOG((LF_GC, LL_INFO1000000, "reachable " LOG_OBJECT_CLASS(*pObjRef))); } } void CALLBACK CalculateSizedRefSize(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { LIMITED_METHOD_CONTRACT; _ASSERTE(pExtraInfo); Object **ppSizedRef = (Object **)pObjRef; size_t* pSize = (size_t *)pExtraInfo; LOG((LF_GC, LL_INFO100000, LOG_HANDLE_OBJECT_CLASS("Getting size of referent of SizedRef-", pObjRef, "to ", *pObjRef))); ScanContext* sc = (ScanContext *)lp1; promote_func* callback = (promote_func*) lp2; size_t sizeBegin = GCHeap::GetGCHeap()->GetPromotedBytes(sc->thread_number); callback(ppSizedRef, (ScanContext *)lp1, 0); size_t sizeEnd = GCHeap::GetGCHeap()->GetPromotedBytes(sc->thread_number); *pSize = sizeEnd - sizeBegin; } /* * Scan callback for updating pointers. * * This callback is called to update pointers for individual objects referred to by * handles in the weak and strong tables. */ void CALLBACK UpdatePointer(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { LIMITED_METHOD_CONTRACT; LOG((LF_GC, LL_INFO100000, LOG_HANDLE_OBJECT("Querying for new location of ", pObjRef, "to ", *pObjRef))); Object **ppRef = (Object **)pObjRef; #ifdef _DEBUG Object *pOldLocation = *ppRef; #endif _ASSERTE(lp2); promote_func* callback = (promote_func*) lp2; callback(ppRef, (ScanContext *)lp1, 0); #ifdef _DEBUG if (pOldLocation != *pObjRef) LOG((LF_GC, LL_INFO10000, "Updating " FMT_HANDLE "from" FMT_ADDR "to " FMT_OBJECT "\n", DBG_ADDR(pObjRef), DBG_ADDR(pOldLocation), DBG_ADDR(*pObjRef))); else LOG((LF_GC, LL_INFO100000, "Updating " FMT_HANDLE "- " FMT_OBJECT "did not move\n", DBG_ADDR(pObjRef), DBG_ADDR(*pObjRef))); #endif } #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) /* * Scan callback for updating pointers. * * This callback is called to update pointers for individual objects referred to by * handles in the weak and strong tables. */ void CALLBACK ScanPointerForProfilerAndETW(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { CONTRACTL { NOTHROW; GC_NOTRIGGER; if (GetThreadNULLOk()) { MODE_COOPERATIVE; } } CONTRACTL_END; LOG((LF_GC | LF_CORPROF, LL_INFO100000, LOG_HANDLE_OBJECT_CLASS("Notifying profiler of ", pObjRef, "to ", *pObjRef))); // Get the baseobject (which can subsequently be cast into an OBJECTREF == ObjectID Object **pRef = (Object **)pObjRef; // Get a hold of the heap ID that's tacked onto the end of the scancontext struct. ProfilingScanContext *pSC = (ProfilingScanContext *)lp1; DWORD rootFlags = 0; BOOL isDependent = FALSE; OBJECTHANDLE handle = (OBJECTHANDLE)(pRef); switch (HandleFetchType(handle)) { case HNDTYPE_DEPENDENT: isDependent = TRUE; break; case HNDTYPE_WEAK_SHORT: case HNDTYPE_WEAK_LONG: #ifdef FEATURE_COMINTEROP case HNDTYPE_WEAK_WINRT: #endif // FEATURE_COMINTEROP rootFlags |= kEtwGCRootFlagsWeakRef; break; case HNDTYPE_STRONG: case HNDTYPE_SIZEDREF: break; case HNDTYPE_PINNED: case HNDTYPE_ASYNCPINNED: rootFlags |= kEtwGCRootFlagsPinning; break; case HNDTYPE_VARIABLE: #if 0 // this feature appears to be unused for now rootFlags |= COR_PRF_GC_ROOT_VARIABLE; #else _ASSERTE(!"Variable handle encountered"); #endif break; #ifdef FEATURE_COMINTEROP case HNDTYPE_REFCOUNTED: rootFlags |= kEtwGCRootFlagsRefCounted; if (*pRef != NULL) { ComCallWrapper* pWrap = ComCallWrapper::GetWrapperForObject((OBJECTREF)*pRef); if (pWrap == NULL || !pWrap->IsWrapperActive()) rootFlags |= kEtwGCRootFlagsWeakRef; } break; #endif // FEATURE_COMINTEROP } _UNCHECKED_OBJECTREF pSec = NULL; #ifdef GC_PROFILING // Give the profiler the objectref. if (pSC->fProfilerPinned) { if (!isDependent) { BEGIN_PIN_PROFILER(CORProfilerTrackGC()); g_profControlBlock.pProfInterface->RootReference2( (BYTE *)*pRef, kEtwGCRootKindHandle, (EtwGCRootFlags)rootFlags, pRef, &pSC->pHeapId); END_PIN_PROFILER(); } else { BEGIN_PIN_PROFILER(CORProfilerTrackConditionalWeakTableElements()); pSec = (_UNCHECKED_OBJECTREF)HndGetHandleExtraInfo(handle); g_profControlBlock.pProfInterface->ConditionalWeakTableElementReference( (BYTE*)*pRef, (BYTE*)pSec, pRef, &pSC->pHeapId); END_PIN_PROFILER(); } } #endif // GC_PROFILING // Notify ETW of the handle if (ETW::GCLog::ShouldWalkHeapRootsForEtw()) { if (isDependent && (pSec == NULL)) { pSec = (_UNCHECKED_OBJECTREF)HndGetHandleExtraInfo(handle); } ETW::GCLog::RootReference( handle, *pRef, // object being rooted pSec, // pSecondaryNodeForDependentHandle isDependent, pSC, 0, // dwGCFlags, rootFlags); // ETW handle flags } } #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) /* * Scan callback for updating pointers. * * This callback is called to update pointers for individual objects referred to by * handles in the pinned table. */ void CALLBACK UpdatePointerPinned(_UNCHECKED_OBJECTREF *pObjRef, LPARAM *pExtraInfo, LPARAM lp1, LPARAM lp2) { LIMITED_METHOD_CONTRACT; Object **ppRef = (Object **)pObjRef; _ASSERTE(lp2); promote_func* callback = (promote_func*) lp2; callback(ppRef, (ScanContext *)lp1, GC_CALL_PINNED); LOG((LF_GC, LL_INFO100000, LOG_HANDLE_OBJECT("Updating ", pObjRef, "to pinned ", *pObjRef))); } //---------------------------------------------------------------------------- // flags describing the handle types static const UINT s_rgTypeFlags[] = { HNDF_NORMAL, // HNDTYPE_WEAK_SHORT HNDF_NORMAL, // HNDTYPE_WEAK_LONG HNDF_NORMAL, // HNDTYPE_STRONG HNDF_NORMAL, // HNDTYPE_PINNED HNDF_EXTRAINFO, // HNDTYPE_VARIABLE HNDF_NORMAL, // HNDTYPE_REFCOUNTED HNDF_EXTRAINFO, // HNDTYPE_DEPENDENT HNDF_NORMAL, // HNDTYPE_ASYNCPINNED HNDF_EXTRAINFO, // HNDTYPE_SIZEDREF HNDF_EXTRAINFO, // HNDTYPE_WEAK_WINRT }; int getNumberOfSlots() { WRAPPER_NO_CONTRACT; // when Ref_Initialize called, GCHeap::GetNumberOfHeaps() is still 0, so use #procs as a workaround // it is legal since even if later #heaps < #procs we create handles by thread home heap // and just have extra unused slots in HandleTableBuckets, which does not take a lot of space if (!GCHeap::IsServerHeap()) return 1; #ifdef FEATURE_REDHAWK return g_SystemInfo.dwNumberOfProcessors; #else return (CPUGroupInfo::CanEnableGCCPUGroups() ? CPUGroupInfo::GetNumActiveProcessors() : g_SystemInfo.dwNumberOfProcessors); #endif } class HandleTableBucketHolder { private: HandleTableBucket* m_bucket; int m_slots; BOOL m_SuppressRelease; public: HandleTableBucketHolder(HandleTableBucket* bucket, int slots); ~HandleTableBucketHolder(); void SuppressRelease() { m_SuppressRelease = TRUE; } }; HandleTableBucketHolder::HandleTableBucketHolder(HandleTableBucket* bucket, int slots) :m_bucket(bucket), m_slots(slots), m_SuppressRelease(FALSE) { } HandleTableBucketHolder::~HandleTableBucketHolder() { if (m_SuppressRelease) { return; } if (m_bucket->pTable) { for (int n = 0; n < m_slots; n ++) { if (m_bucket->pTable[n]) { HndDestroyHandleTable(m_bucket->pTable[n]); } } delete [] m_bucket->pTable; } delete m_bucket; } bool Ref_Initialize() { CONTRACTL { NOTHROW; WRAPPER(GC_NOTRIGGER); INJECT_FAULT(return false); } CONTRACTL_END; // sanity _ASSERTE(g_HandleTableMap.pBuckets == NULL); // Create an array of INITIAL_HANDLE_TABLE_ARRAY_SIZE HandleTableBuckets to hold the handle table sets NewHolder pBuckets(new (nothrow) HandleTableBucket * [ INITIAL_HANDLE_TABLE_ARRAY_SIZE ]); if (pBuckets == NULL) return false; ZeroMemory(pBuckets, INITIAL_HANDLE_TABLE_ARRAY_SIZE * sizeof (HandleTableBucket *)); // Crate the first bucket HandleTableBucket * pBucket = new (nothrow) HandleTableBucket; if (pBucket == NULL) return false; pBucket->HandleTableIndex = 0; int n_slots = getNumberOfSlots(); HandleTableBucketHolder bucketHolder(pBucket, n_slots); // create the handle table set for the first bucket pBucket->pTable = new (nothrow) HHANDLETABLE [ n_slots ]; if (pBucket->pTable == NULL) return false; ZeroMemory(pBucket->pTable, n_slots * sizeof (HHANDLETABLE)); for (int uCPUindex=0; uCPUindex < n_slots; uCPUindex++) { pBucket->pTable[uCPUindex] = HndCreateHandleTable(s_rgTypeFlags, _countof(s_rgTypeFlags), ADIndex(1)); if (pBucket->pTable[uCPUindex] == NULL) return false; HndSetHandleTableIndex(pBucket->pTable[uCPUindex], 0); } pBuckets[0] = pBucket; bucketHolder.SuppressRelease(); g_HandleTableMap.pBuckets = pBuckets; g_HandleTableMap.dwMaxIndex = INITIAL_HANDLE_TABLE_ARRAY_SIZE; g_HandleTableMap.pNext = NULL; pBuckets.SuppressRelease(); // Allocate contexts used during dependent handle promotion scanning. There's one of these for every GC // heap since they're scanned in parallel. g_pDependentHandleContexts = new (nothrow) DhContext[n_slots]; if (g_pDependentHandleContexts == NULL) return false; return true; } void Ref_Shutdown() { WRAPPER_NO_CONTRACT; if (g_pDependentHandleContexts) { delete [] g_pDependentHandleContexts; g_pDependentHandleContexts = NULL; } // are there any handle tables? if (g_HandleTableMap.pBuckets) { // don't destroy any of the indexed handle tables; they should // be destroyed externally. // destroy the global handle table bucket tables Ref_DestroyHandleTableBucket(g_HandleTableMap.pBuckets[0]); // destroy the handle table bucket array HandleTableMap *walk = &g_HandleTableMap; while (walk) { delete [] walk->pBuckets; walk = walk->pNext; } // null out the handle table array g_HandleTableMap.pNext = NULL; g_HandleTableMap.dwMaxIndex = 0; // null out the global table handle g_HandleTableMap.pBuckets = NULL; } } #ifndef FEATURE_REDHAWK // ATTENTION: interface changed // Note: this function called only from AppDomain::Init() HandleTableBucket *Ref_CreateHandleTableBucket(ADIndex uADIndex) { CONTRACTL { THROWS; WRAPPER(GC_TRIGGERS); INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; HandleTableBucket *result = NULL; HandleTableMap *walk; walk = &g_HandleTableMap; HandleTableMap *last = NULL; UINT offset = 0; result = new HandleTableBucket; result->pTable = NULL; // create handle table set for the bucket int n_slots = getNumberOfSlots(); HandleTableBucketHolder bucketHolder(result, n_slots); result->pTable = new HHANDLETABLE [ n_slots ]; ZeroMemory(result->pTable, n_slots * sizeof (HHANDLETABLE)); for (int uCPUindex=0; uCPUindex < n_slots; uCPUindex++) { result->pTable[uCPUindex] = HndCreateHandleTable(s_rgTypeFlags, _countof(s_rgTypeFlags), uADIndex); if (!result->pTable[uCPUindex]) COMPlusThrowOM(); } for (;;) { // Do we have free slot while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] == 0) { for (int uCPUindex=0; uCPUindex < n_slots; uCPUindex++) HndSetHandleTableIndex(result->pTable[uCPUindex], i+offset); result->HandleTableIndex = i+offset; if (FastInterlockCompareExchangePointer(&walk->pBuckets[i], result, NULL) == 0) { // Get a free slot. bucketHolder.SuppressRelease(); return result; } } } last = walk; offset = walk->dwMaxIndex; walk = walk->pNext; } // No free slot. // Let's create a new node NewHolder newMap; newMap = new HandleTableMap; newMap->pBuckets = new HandleTableBucket * [ INITIAL_HANDLE_TABLE_ARRAY_SIZE ]; newMap.SuppressRelease(); newMap->dwMaxIndex = last->dwMaxIndex + INITIAL_HANDLE_TABLE_ARRAY_SIZE; newMap->pNext = NULL; ZeroMemory(newMap->pBuckets, INITIAL_HANDLE_TABLE_ARRAY_SIZE * sizeof (HandleTableBucket *)); if (FastInterlockCompareExchangePointer(&last->pNext, newMap.GetValue(), NULL) != NULL) { // This thread loses. delete [] newMap->pBuckets; delete newMap; } walk = last->pNext; offset = last->dwMaxIndex; } } #endif // !FEATURE_REDHAWK void Ref_RemoveHandleTableBucket(HandleTableBucket *pBucket) { LIMITED_METHOD_CONTRACT; size_t index = pBucket->HandleTableIndex; HandleTableMap* walk = &g_HandleTableMap; size_t offset = 0; while (walk) { if ((index < walk->dwMaxIndex) && (index >= offset)) { // During AppDomain unloading, we first remove a handle table and then destroy // the table. As soon as the table is removed, the slot can be reused. if (walk->pBuckets[index - offset] == pBucket) { walk->pBuckets[index - offset] = NULL; return; } } offset = walk->dwMaxIndex; walk = walk->pNext; } // Didn't find it. This will happen typically from Ref_DestroyHandleTableBucket if // we explicitly call Ref_RemoveHandleTableBucket first. } void Ref_DestroyHandleTableBucket(HandleTableBucket *pBucket) { WRAPPER_NO_CONTRACT; // this check is because here we might be called from AppDomain::Terminate after AppDomain::ClearGCRoots, // which calls Ref_RemoveHandleTableBucket itself Ref_RemoveHandleTableBucket(pBucket); for (int uCPUindex=0; uCPUindex < getNumberOfSlots(); uCPUindex++) { HndDestroyHandleTable(pBucket->pTable[uCPUindex]); } delete [] pBucket->pTable; delete pBucket; } int getSlotNumber(ScanContext* sc) { WRAPPER_NO_CONTRACT; return (GCHeap::IsServerHeap() ? sc->thread_number : 0); } // - reexpress as complete only like hndtable does now!!! -fmh void Ref_EndSynchronousGC(UINT condemned, UINT maxgen) { LIMITED_METHOD_CONTRACT; // NOT used, must be modified for MTHTS (scalable HandleTable scan) if planned to use: // need to pass ScanContext info to split HT bucket by threads, or to be performed under t_join::join /* // tell the table we finished a GC HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { HHANDLETABLE hTable = walk->pTable[i]; if (hTable) HndNotifyGcCycleComplete(hTable, condemned, maxgen); } walk = walk->pNext; } */ } OBJECTHANDLE CreateDependentHandle(HHANDLETABLE table, OBJECTREF primary, OBJECTREF secondary) { CONTRACTL { THROWS; GC_NOTRIGGER; MODE_COOPERATIVE; } CONTRACTL_END; OBJECTHANDLE handle = HndCreateHandle(table, HNDTYPE_DEPENDENT, primary); SetDependentHandleSecondary(handle, secondary); return handle; } void SetDependentHandleSecondary(OBJECTHANDLE handle, OBJECTREF objref) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; MODE_COOPERATIVE; } CONTRACTL_END; // sanity _ASSERTE(handle); #ifdef _DEBUG // handle should not be in unloaded domain ValidateAppDomainForHandle(handle); // Make sure the objref is valid before it is assigned to a handle ValidateAssignObjrefForHandle(objref, HndGetHandleTableADIndex(HndGetHandleTable(handle))); #endif // unwrap the objectref we were given _UNCHECKED_OBJECTREF value = OBJECTREF_TO_UNCHECKED_OBJECTREF(objref); // if we are doing a non-NULL pointer store then invoke the write-barrier if (value) HndWriteBarrier(handle, objref); // store the pointer HndSetHandleExtraInfo(handle, HNDTYPE_DEPENDENT, (LPARAM)value); } //---------------------------------------------------------------------------- /* * CreateVariableHandle. * * Creates a variable-strength handle. * * N.B. This routine is not a macro since we do validation in RETAIL. * We always validate the type here because it can come from external callers. */ OBJECTHANDLE CreateVariableHandle(HHANDLETABLE hTable, OBJECTREF object, UINT type) { WRAPPER_NO_CONTRACT; // verify that we are being asked to create a valid type if (!IS_VALID_VHT_VALUE(type)) { // bogus value passed in _ASSERTE(FALSE); return NULL; } // create the handle return HndCreateHandle(hTable, HNDTYPE_VARIABLE, object, (LPARAM)type); } /* * UpdateVariableHandleType. * * Changes the dynamic type of a variable-strength handle. * * N.B. This routine is not a macro since we do validation in RETAIL. * We always validate the type here because it can come from external callers. */ void UpdateVariableHandleType(OBJECTHANDLE handle, UINT type) { WRAPPER_NO_CONTRACT; // verify that we are being asked to set a valid type if (!IS_VALID_VHT_VALUE(type)) { // bogus value passed in _ASSERTE(FALSE); return; } // (francish) CONCURRENT GC NOTE // // If/when concurrent GC is implemented, we need to make sure variable handles // DON'T change type during an asynchronous scan, OR that we properly recover // from the change. Some changes are benign, but for example changing to or // from a pinning handle in the middle of a scan would not be fun. // // store the type in the handle's extra info HndSetHandleExtraInfo(handle, HNDTYPE_VARIABLE, (LPARAM)type); } /* * TraceVariableHandles. * * Convenience function for tracing variable-strength handles. * Wraps HndScanHandlesForGC. */ void TraceVariableHandles(HANDLESCANPROC pfnTrace, LPARAM lp1, LPARAM lp2, UINT uEnableMask, UINT condemned, UINT maxgen, UINT flags) { WRAPPER_NO_CONTRACT; // set up to scan variable handles with the specified mask and trace function UINT type = HNDTYPE_VARIABLE; struct VARSCANINFO info = { (LPARAM)uEnableMask, pfnTrace, lp2 }; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber((ScanContext*) lp1)]; if (hTable) { #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING if (g_fEnableARM) { ScanContext* sc = (ScanContext *)lp1; sc->pCurrentDomain = SystemDomain::GetAppDomainAtIndex(HndGetHandleTableADIndex(hTable)); } #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING HndScanHandlesForGC(hTable, VariableTraceDispatcher, lp1, (LPARAM)&info, &type, 1, condemned, maxgen, HNDGCF_EXTRAINFO | flags); } } walk = walk->pNext; } } /* loop scan version of TraceVariableHandles for single-thread-managed Ref_* functions should be kept in sync with the code above */ void TraceVariableHandlesBySingleThread(HANDLESCANPROC pfnTrace, LPARAM lp1, LPARAM lp2, UINT uEnableMask, UINT condemned, UINT maxgen, UINT flags) { WRAPPER_NO_CONTRACT; // set up to scan variable handles with the specified mask and trace function UINT type = HNDTYPE_VARIABLE; struct VARSCANINFO info = { (LPARAM)uEnableMask, pfnTrace, lp2 }; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { // this is the one of Ref_* function performed by single thread in MULTI_HEAPS case, so we need to loop through all HT of the bucket for (int uCPUindex=0; uCPUindex < getNumberOfSlots(); uCPUindex++) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) HndScanHandlesForGC(hTable, VariableTraceDispatcher, lp1, (LPARAM)&info, &type, 1, condemned, maxgen, HNDGCF_EXTRAINFO | flags); } } walk = walk->pNext; } } //---------------------------------------------------------------------------- void Ref_TracePinningRoots(UINT condemned, UINT maxgen, ScanContext* sc, Ref_promote_func* fn) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Pinning referents of pinned handles in generation %u\n", condemned)); // pin objects pointed to by pinning handles UINT types[2] = {HNDTYPE_PINNED, HNDTYPE_ASYNCPINNED}; UINT flags = sc->concurrent ? HNDGCF_ASYNC : HNDGCF_NORMAL; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber((ScanContext*) sc)]; if (hTable) { #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING if (g_fEnableARM) { sc->pCurrentDomain = SystemDomain::GetAppDomainAtIndex(HndGetHandleTableADIndex(hTable)); } #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING HndScanHandlesForGC(hTable, PinObject, LPARAM(sc), LPARAM(fn), types, _countof(types), condemned, maxgen, flags); } } walk = walk->pNext; } // pin objects pointed to by variable handles whose dynamic type is VHT_PINNED TraceVariableHandles(PinObject, LPARAM(sc), LPARAM(fn), VHT_PINNED, condemned, maxgen, flags); } void Ref_TraceNormalRoots(UINT condemned, UINT maxgen, ScanContext* sc, Ref_promote_func* fn) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Promoting referents of strong handles in generation %u\n", condemned)); // promote objects pointed to by strong handles // during ephemeral GCs we also want to promote the ones pointed to by sizedref handles. UINT types[2] = {HNDTYPE_STRONG, HNDTYPE_SIZEDREF}; UINT uTypeCount = (((condemned >= maxgen) && !GCHeap::GetGCHeap()->IsConcurrentGCInProgress()) ? 1 : _countof(types)); UINT flags = (sc->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(sc)]; if (hTable) { #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING if (g_fEnableARM) { sc->pCurrentDomain = SystemDomain::GetAppDomainAtIndex(HndGetHandleTableADIndex(hTable)); } #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING HndScanHandlesForGC(hTable, PromoteObject, LPARAM(sc), LPARAM(fn), types, uTypeCount, condemned, maxgen, flags); } } walk = walk->pNext; } // promote objects pointed to by variable handles whose dynamic type is VHT_STRONG TraceVariableHandles(PromoteObject, LPARAM(sc), LPARAM(fn), VHT_STRONG, condemned, maxgen, flags); #ifdef FEATURE_COMINTEROP // don't scan ref-counted handles during concurrent phase as the clean-up of CCWs can race with AD unload and cause AV's if (!sc->concurrent) { // promote ref-counted handles UINT type = HNDTYPE_REFCOUNTED; walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(sc)]; if (hTable) HndScanHandlesForGC(hTable, PromoteRefCounted, LPARAM(sc), LPARAM(fn), &type, 1, condemned, maxgen, flags ); } walk = walk->pNext; } } #endif // FEATURE_COMINTEROP } #ifdef FEATURE_COMINTEROP void Ref_TraceRefCountHandles(HANDLESCANPROC callback, LPARAM lParam1, LPARAM lParam2) { int max_slots = getNumberOfSlots(); UINT handleType = HNDTYPE_REFCOUNTED; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i++) { if (walk->pBuckets[i] != NULL) { for (int j = 0; j < max_slots; j++) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[j]; if (hTable) HndEnumHandles(hTable, &handleType, 1, callback, lParam1, lParam2, FALSE); } } } walk = walk->pNext; } } #endif void Ref_CheckReachable(UINT condemned, UINT maxgen, LPARAM lp1) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Checking reachability of referents of long-weak handles in generation %u\n", condemned)); // these are the handle types that need to be checked UINT types[] = { HNDTYPE_WEAK_LONG, #ifdef FEATURE_COMINTEROP HNDTYPE_REFCOUNTED, #endif // FEATURE_COMINTEROP }; // check objects pointed to by short weak handles UINT flags = (((ScanContext*) lp1)->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; int uCPUindex = getSlotNumber((ScanContext*) lp1); HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) HndScanHandlesForGC(hTable, CheckPromoted, lp1, 0, types, _countof(types), condemned, maxgen, flags); } } walk = walk->pNext; } // check objects pointed to by variable handles whose dynamic type is VHT_WEAK_LONG TraceVariableHandles(CheckPromoted, lp1, 0, VHT_WEAK_LONG, condemned, maxgen, flags); } // // Dependent handles manages the relationship between primary and secondary objects, where the lifetime of // the secondary object is dependent upon that of the primary. The handle itself holds the primary instance, // while the extra handle info holds the secondary object. The secondary object should always be promoted // when the primary is, and the handle should be cleared if the primary is not promoted. Can't use ordinary // strong handle to refer to the secondary as this could case a cycle in the graph if the secondary somehow // pointed back to the primary. Can't use weak handle because that would not keep the secondary object alive. // // The result is that a dependenHandle has the EFFECT of // * long weak handles in both the primary and secondary objects // * a strong reference from the primary object to the secondary one // // Dependent handles are currently used for // // * managing fields added to EnC classes, where the handle itself holds the this pointer and the // secondary object represents the new field that was added. // * it is exposed to managed code (as System.Runtime.CompilerServices.DependentHandle) and is used in the // implementation of ConditionWeakTable. // // Retrieve the dependent handle context associated with the current GC scan context. DhContext *Ref_GetDependentHandleContext(ScanContext* sc) { WRAPPER_NO_CONTRACT; return &g_pDependentHandleContexts[getSlotNumber(sc)]; } // Scan the dependent handle table promoting any secondary object whose associated primary object is promoted. // // Multiple scans may be required since (a) secondary promotions made during one scan could cause the primary // of another handle to be promoted and (b) the GC may not have marked all promoted objects at the time it // initially calls us. // // Returns true if any promotions resulted from this scan. bool Ref_ScanDependentHandlesForPromotion(DhContext *pDhContext) { LOG((LF_GC, LL_INFO10000, "Checking liveness of referents of dependent handles in generation %u\n", pDhContext->m_iCondemned)); UINT type = HNDTYPE_DEPENDENT; UINT flags = (pDhContext->m_pScanContext->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; flags |= HNDGCF_EXTRAINFO; // Keep a note of whether we promoted anything over the entire scan (not just the last iteration). We need // to return this data since under server GC promotions from this table may cause further promotions in // tables handled by other threads. bool fAnyPromotions = false; // Keep rescanning the table while both the following conditions are true: // 1) There's at least primary object left that could have been promoted. // 2) We performed at least one secondary promotion (which could have caused a primary promotion) on the // last scan. // Note that even once we terminate the GC may call us again (because it has caused more objects to be // marked as promoted). But we scan in a loop here anyway because it is cheaper for us to loop than the GC // (especially on server GC where each external cycle has to be synchronized between GC worker threads). do { // Assume the conditions for re-scanning are both false initially. The scan callback below // (PromoteDependentHandle) will set the relevant flag on the first unpromoted primary it sees or // secondary promotion it performs. pDhContext->m_fUnpromotedPrimaries = false; pDhContext->m_fPromoted = false; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(pDhContext->m_pScanContext)]; if (hTable) { HndScanHandlesForGC(hTable, PromoteDependentHandle, LPARAM(pDhContext->m_pScanContext), LPARAM(pDhContext->m_pfnPromoteFunction), &type, 1, pDhContext->m_iCondemned, pDhContext->m_iMaxGen, flags ); } } } walk = walk->pNext; } if (pDhContext->m_fPromoted) fAnyPromotions = true; } while (pDhContext->m_fUnpromotedPrimaries && pDhContext->m_fPromoted); return fAnyPromotions; } // Perform a scan of dependent handles for the purpose of clearing any that haven't had their primary // promoted. void Ref_ScanDependentHandlesForClearing(UINT condemned, UINT maxgen, ScanContext* sc, Ref_promote_func* fn) { LOG((LF_GC, LL_INFO10000, "Clearing dead dependent handles in generation %u\n", condemned)); UINT type = HNDTYPE_DEPENDENT; UINT flags = (sc->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; flags |= HNDGCF_EXTRAINFO; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(sc)]; if (hTable) { HndScanHandlesForGC(hTable, ClearDependentHandle, LPARAM(sc), LPARAM(fn), &type, 1, condemned, maxgen, flags ); } } } walk = walk->pNext; } } // Perform a scan of dependent handles for the purpose of updating handles to track relocated objects. void Ref_ScanDependentHandlesForRelocation(UINT condemned, UINT maxgen, ScanContext* sc, Ref_promote_func* fn) { LOG((LF_GC, LL_INFO10000, "Relocating moved dependent handles in generation %u\n", condemned)); UINT type = HNDTYPE_DEPENDENT; UINT flags = (sc->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; flags |= HNDGCF_EXTRAINFO; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(sc)]; if (hTable) { HndScanHandlesForGC(hTable, UpdateDependentHandle, LPARAM(sc), LPARAM(fn), &type, 1, condemned, maxgen, flags ); } } } walk = walk->pNext; } } /* loop scan version of TraceVariableHandles for single-thread-managed Ref_* functions should be kept in sync with the code above */ void TraceDependentHandlesBySingleThread(HANDLESCANPROC pfnTrace, LPARAM lp1, UINT condemned, UINT maxgen, UINT flags) { WRAPPER_NO_CONTRACT; // set up to scan variable handles with the specified mask and trace function UINT type = HNDTYPE_DEPENDENT; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { // this is the one of Ref_* function performed by single thread in MULTI_HEAPS case, so we need to loop through all HT of the bucket for (int uCPUindex=0; uCPUindex < getNumberOfSlots(); uCPUindex++) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) HndScanHandlesForGC(hTable, TraceDependentHandle, lp1, (LPARAM)pfnTrace, &type, 1, condemned, maxgen, HNDGCF_EXTRAINFO | flags); } } walk = walk->pNext; } } // We scan handle tables by their buckets (ie, AD index). We could get into the situation where // the AD indices are not very compacted (for example if we have just unloaded ADs and their // indices haven't been reused yet) and we could be scanning them in an unbalanced fashion. // Consider using an array to represent the compacted form of all AD indices exist for the // sized ref handles. void ScanSizedRefByAD(UINT maxgen, HANDLESCANPROC scanProc, ScanContext* sc, Ref_promote_func* fn, UINT flags) { HandleTableMap *walk = &g_HandleTableMap; UINT type = HNDTYPE_SIZEDREF; int uCPUindex = getSlotNumber(sc); int n_slots = GCHeap::GetGCHeap()->GetNumberOfHeaps(); while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { ADIndex adIndex = HndGetHandleTableADIndex(walk->pBuckets[i]->pTable[0]); if ((adIndex.m_dwIndex % n_slots) == (DWORD)uCPUindex) { for (int index = 0; index < n_slots; index++) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[index]; if (hTable) { #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING if (g_fEnableARM) { sc->pCurrentDomain = SystemDomain::GetAppDomainAtIndex(adIndex); } #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING HndScanHandlesForGC(hTable, scanProc, LPARAM(sc), LPARAM(fn), &type, 1, maxgen, maxgen, flags); } } } } } walk = walk->pNext; } } void ScanSizedRefByCPU(UINT maxgen, HANDLESCANPROC scanProc, ScanContext* sc, Ref_promote_func* fn, UINT flags) { HandleTableMap *walk = &g_HandleTableMap; UINT type = HNDTYPE_SIZEDREF; int uCPUindex = getSlotNumber(sc); while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) { #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING if (g_fEnableARM) { sc->pCurrentDomain = SystemDomain::GetAppDomainAtIndex(HndGetHandleTableADIndex(hTable)); } #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING HndScanHandlesForGC(hTable, scanProc, LPARAM(sc), LPARAM(fn), &type, 1, maxgen, maxgen, flags); } } } walk = walk->pNext; } } void Ref_ScanSizedRefHandles(UINT condemned, UINT maxgen, ScanContext* sc, Ref_promote_func* fn) { LOG((LF_GC, LL_INFO10000, "Scanning SizedRef handles to in generation %u\n", condemned)); _ASSERTE (condemned == maxgen); UINT flags = (sc->concurrent ? HNDGCF_ASYNC : HNDGCF_NORMAL) | HNDGCF_EXTRAINFO; ScanSizedRefByCPU(maxgen, CalculateSizedRefSize, sc, fn, flags); } void Ref_CheckAlive(UINT condemned, UINT maxgen, LPARAM lp1) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Checking liveness of referents of short-weak handles in generation %u\n", condemned)); // perform a multi-type scan that checks for unreachable objects UINT types[] = { HNDTYPE_WEAK_SHORT #ifdef FEATURE_COMINTEROP , HNDTYPE_WEAK_WINRT #endif // FEATURE_COMINTEROP }; UINT flags = (((ScanContext*) lp1)->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; int uCPUindex = getSlotNumber((ScanContext*) lp1); HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) HndScanHandlesForGC(hTable, CheckPromoted, lp1, 0, types, _countof(types), condemned, maxgen, flags); } } walk = walk->pNext; } // check objects pointed to by variable handles whose dynamic type is VHT_WEAK_SHORT TraceVariableHandles(CheckPromoted, lp1, 0, VHT_WEAK_SHORT, condemned, maxgen, flags); } static VOLATILE(LONG) uCount = 0; // NOTE: Please: if you update this function, update the very similar profiling function immediately below!!! void Ref_UpdatePointers(UINT condemned, UINT maxgen, ScanContext* sc, Ref_promote_func* fn) { WRAPPER_NO_CONTRACT; // For now, treat the syncblock as if it were short weak handles. Later, get // the benefits of fast allocation / free & generational awareness by supporting // the SyncTable as a new block type. // @TODO cwb: wait for compelling performance measurements. BOOL bDo = TRUE; if (GCHeap::IsServerHeap()) { bDo = (FastInterlockIncrement(&uCount) == 1); FastInterlockCompareExchange (&uCount, 0, GCHeap::GetGCHeap()->GetNumberOfHeaps()); _ASSERTE (uCount <= GCHeap::GetGCHeap()->GetNumberOfHeaps()); } if (bDo) GCToEEInterface::SyncBlockCacheWeakPtrScan(&UpdatePointer, LPARAM(sc), LPARAM(fn)); LOG((LF_GC, LL_INFO10000, "Updating pointers to referents of non-pinning handles in generation %u\n", condemned)); // these are the handle types that need their pointers updated UINT types[] = { HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, #ifdef FEATURE_COMINTEROP HNDTYPE_REFCOUNTED, HNDTYPE_WEAK_WINRT, #endif // FEATURE_COMINTEROP HNDTYPE_SIZEDREF, }; // perform a multi-type scan that updates pointers UINT flags = (sc->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(sc)]; if (hTable) HndScanHandlesForGC(hTable, UpdatePointer, LPARAM(sc), LPARAM(fn), types, _countof(types), condemned, maxgen, flags); } walk = walk->pNext; } // update pointers in variable handles whose dynamic type is VHT_WEAK_SHORT, VHT_WEAK_LONG or VHT_STRONG TraceVariableHandles(UpdatePointer, LPARAM(sc), LPARAM(fn), VHT_WEAK_SHORT | VHT_WEAK_LONG | VHT_STRONG, condemned, maxgen, flags); } #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) // Please update this if you change the Ref_UpdatePointers function above. void Ref_ScanPointersForProfilerAndETW(UINT maxgen, LPARAM lp1) { WRAPPER_NO_CONTRACT; LOG((LF_GC | LF_CORPROF, LL_INFO10000, "Scanning all handle roots for profiler.\n")); // Don't scan the sync block because they should not be reported. They are weak handles only // We should change the following to not report weak either // these are the handle types that need their pointers updated UINT types[] = { HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, #ifdef FEATURE_COMINTEROP HNDTYPE_REFCOUNTED, HNDTYPE_WEAK_WINRT, #endif // FEATURE_COMINTEROP, HNDTYPE_PINNED, // HNDTYPE_VARIABLE, HNDTYPE_ASYNCPINNED, HNDTYPE_SIZEDREF, }; UINT flags = HNDGCF_NORMAL; // perform a multi-type scan that updates pointers HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) // this is the one of Ref_* function performed by single thread in MULTI_HEAPS case, so we need to loop through all HT of the bucket for (int uCPUindex=0; uCPUindex < getNumberOfSlots(); uCPUindex++) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) HndScanHandlesForGC(hTable, &ScanPointerForProfilerAndETW, lp1, 0, types, _countof(types), maxgen, maxgen, flags); } walk = walk->pNext; } // update pointers in variable handles whose dynamic type is VHT_WEAK_SHORT, VHT_WEAK_LONG or VHT_STRONG TraceVariableHandlesBySingleThread(&ScanPointerForProfilerAndETW, lp1, 0, VHT_WEAK_SHORT | VHT_WEAK_LONG | VHT_STRONG, maxgen, maxgen, flags); } void Ref_ScanDependentHandlesForProfilerAndETW(UINT maxgen, ProfilingScanContext * SC) { WRAPPER_NO_CONTRACT; LOG((LF_GC | LF_CORPROF, LL_INFO10000, "Scanning dependent handles for profiler.\n")); UINT flags = HNDGCF_NORMAL; LPARAM lp1 = (LPARAM)SC; // we'll re-use pHeapId (which was either unused (0) or freed by EndRootReferences2 // (-1)), so reset it to NULL _ASSERTE((*((size_t *)(&SC->pHeapId)) == (size_t)(-1)) || (*((size_t *)(&SC->pHeapId)) == (size_t)(0))); SC->pHeapId = NULL; TraceDependentHandlesBySingleThread(&ScanPointerForProfilerAndETW, lp1, maxgen, maxgen, flags); } #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) void Ref_UpdatePinnedPointers(UINT condemned, UINT maxgen, ScanContext* sc, Ref_promote_func* fn) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Updating pointers to referents of pinning handles in generation %u\n", condemned)); // these are the handle types that need their pointers updated UINT types[2] = {HNDTYPE_PINNED, HNDTYPE_ASYNCPINNED}; UINT flags = (sc->concurrent) ? HNDGCF_ASYNC : HNDGCF_NORMAL; HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(sc)]; if (hTable) HndScanHandlesForGC(hTable, UpdatePointerPinned, LPARAM(sc), LPARAM(fn), types, _countof(types), condemned, maxgen, flags); } walk = walk->pNext; } // update pointers in variable handles whose dynamic type is VHT_PINNED TraceVariableHandles(UpdatePointerPinned, LPARAM(sc), LPARAM(fn), VHT_PINNED, condemned, maxgen, flags); } void Ref_AgeHandles(UINT condemned, UINT maxgen, LPARAM lp1) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Aging handles in generation %u\n", condemned)); // these are the handle types that need their ages updated UINT types[] = { HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, HNDTYPE_PINNED, HNDTYPE_VARIABLE, #ifdef FEATURE_COMINTEROP HNDTYPE_REFCOUNTED, HNDTYPE_WEAK_WINRT, #endif // FEATURE_COMINTEROP HNDTYPE_ASYNCPINNED, HNDTYPE_SIZEDREF, }; int uCPUindex = getSlotNumber((ScanContext*) lp1); // perform a multi-type scan that ages the handles HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) HndScanHandlesForGC(hTable, NULL, 0, 0, types, _countof(types), condemned, maxgen, HNDGCF_AGE); } walk = walk->pNext; } } void Ref_RejuvenateHandles(UINT condemned, UINT maxgen, LPARAM lp1) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Rejuvenating handles.\n")); // these are the handle types that need their ages updated UINT types[] = { HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, HNDTYPE_PINNED, HNDTYPE_VARIABLE, #ifdef FEATURE_COMINTEROP HNDTYPE_REFCOUNTED, HNDTYPE_WEAK_WINRT, #endif // FEATURE_COMINTEROP HNDTYPE_ASYNCPINNED, HNDTYPE_SIZEDREF, }; int uCPUindex = getSlotNumber((ScanContext*) lp1); // reset the ages of these handles HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[uCPUindex]; if (hTable) HndResetAgeMap(hTable, types, _countof(types), condemned, maxgen, HNDGCF_NORMAL); } walk = walk->pNext; } } void Ref_VerifyHandleTable(UINT condemned, UINT maxgen, ScanContext* sc) { WRAPPER_NO_CONTRACT; LOG((LF_GC, LL_INFO10000, "Verifying handles.\n")); // these are the handle types that need to be verified UINT types[] = { HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, HNDTYPE_PINNED, HNDTYPE_VARIABLE, #ifdef FEATURE_COMINTEROP HNDTYPE_REFCOUNTED, HNDTYPE_WEAK_WINRT, #endif // FEATURE_COMINTEROP HNDTYPE_ASYNCPINNED, HNDTYPE_SIZEDREF, }; // verify these handles HandleTableMap *walk = &g_HandleTableMap; while (walk) { for (UINT i = 0; i < INITIAL_HANDLE_TABLE_ARRAY_SIZE; i ++) { if (walk->pBuckets[i] != NULL) { HHANDLETABLE hTable = walk->pBuckets[i]->pTable[getSlotNumber(sc)]; if (hTable) HndVerifyTable(hTable, types, _countof(types), condemned, maxgen, HNDGCF_NORMAL); } } walk = walk->pNext; } } int GetCurrentThreadHomeHeapNumber() { WRAPPER_NO_CONTRACT; if (!GCHeap::IsGCHeapInitialized()) return 0; return GCHeap::GetGCHeap()->GetHomeHeapNumber(); } bool HandleTableBucket::Contains(OBJECTHANDLE handle) { LIMITED_METHOD_CONTRACT; if (NULL == handle) { return FALSE; } HHANDLETABLE hTable = HndGetHandleTable(handle); for (int uCPUindex=0; uCPUindex < GCHeap::GetGCHeap()->GetNumberOfHeaps(); uCPUindex++) { if (hTable == this->pTable[uCPUindex]) { return TRUE; } } return FALSE; } void DestroySizedRefHandle(OBJECTHANDLE handle) { CONTRACTL { NOTHROW; GC_NOTRIGGER; SO_TOLERANT; MODE_ANY; } CONTRACTL_END; HHANDLETABLE hTable = HndGetHandleTable(handle); HndDestroyHandle(hTable , HNDTYPE_SIZEDREF, handle); AppDomain* pDomain = SystemDomain::GetAppDomainAtIndex(HndGetHandleTableADIndex(hTable)); pDomain->DecNumSizedRefHandles(); } #ifdef FEATURE_COMINTEROP void DestroyWinRTWeakHandle(OBJECTHANDLE handle) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; CAN_TAKE_LOCK; SO_TOLERANT; } CONTRACTL_END; // Release the WinRT weak reference if we have one. We're assuming that this will not reenter the // runtime, since if we are pointing at a managed object, we should not be using a HNDTYPE_WEAK_WINRT // but rather a HNDTYPE_WEAK_SHORT or HNDTYPE_WEAK_LONG. IWeakReference* pWinRTWeakReference = reinterpret_cast(HndGetHandleExtraInfo(handle)); if (pWinRTWeakReference != nullptr) { pWinRTWeakReference->Release(); } HndDestroyHandle(HndGetHandleTable(handle), HNDTYPE_WEAK_WINRT, handle); } #endif // FEATURE_COMINTEROP #endif // !DACCESS_COMPILE OBJECTREF GetDependentHandleSecondary(OBJECTHANDLE handle) { WRAPPER_NO_CONTRACT; return UNCHECKED_OBJECTREF_TO_OBJECTREF((_UNCHECKED_OBJECTREF)HndGetHandleExtraInfo(handle)); }