diff options
Diffstat (limited to 'src/gc/handletable.cpp')
-rw-r--r-- | src/gc/handletable.cpp | 1474 |
1 files changed, 1474 insertions, 0 deletions
diff --git a/src/gc/handletable.cpp b/src/gc/handletable.cpp new file mode 100644 index 0000000000..43b43ffcea --- /dev/null +++ b/src/gc/handletable.cpp @@ -0,0 +1,1474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/* + * Generational GC handle manager. Main Entrypoint Layer. + * + * Implements generic support for external roots into a GC heap. + * + + * + */ + +#include "common.h" + +#include "gcenv.h" + +#include "gc.h" + +#include "objecthandle.h" +#include "handletablepriv.h" + +#ifndef FEATURE_REDHAWK +#include "nativeoverlapped.h" +#endif + +/**************************************************************************** + * + * FORWARD DECLARATIONS + * + ****************************************************************************/ + +#ifdef _DEBUG +void DEBUG_PostGCScanHandler(HandleTable *pTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, ScanCallbackInfo *info); +void DEBUG_LogScanningStatistics(HandleTable *pTable, uint32_t level); +#endif + +/*--------------------------------------------------------------------------*/ + + + +/**************************************************************************** + * + * HELPER ROUTINES + * + ****************************************************************************/ + +/* + * Table + * + * Gets and validates the table pointer from a table handle. + * + */ +__inline PTR_HandleTable Table(HHANDLETABLE hTable) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + // convert the handle to a pointer + PTR_HandleTable pTable = (PTR_HandleTable)hTable; + + // sanity + _ASSERTE(pTable); + + // return the table pointer + return pTable; +} + +/*--------------------------------------------------------------------------*/ + + + +/**************************************************************************** + * + * MAIN ENTRYPOINTS + * + ****************************************************************************/ +#ifndef DACCESS_COMPILE +/* + * HndCreateHandleTable + * + * Alocates and initializes a handle table. + * + */ +HHANDLETABLE HndCreateHandleTable(const uint32_t *pTypeFlags, uint32_t uTypeCount, ADIndex uADIndex) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + INJECT_FAULT(return NULL); + } + CONTRACTL_END; + + // sanity + _ASSERTE(uTypeCount); + + // verify that we can handle the specified number of types + // may need to increase HANDLE_MAX_INTERNAL_TYPES (by 4) + _ASSERTE(uTypeCount <= HANDLE_MAX_PUBLIC_TYPES); + + // verify that segment header layout we're using fits expected size + _ASSERTE(sizeof(_TableSegmentHeader) <= HANDLE_HEADER_SIZE); + // if you hit this then TABLE LAYOUT IS BROKEN + + // compute the size of the handle table allocation + uint32_t dwSize = sizeof(HandleTable) + (uTypeCount * sizeof(HandleTypeCache)); + + // allocate the table + HandleTable *pTable = (HandleTable *) new (nothrow) uint8_t[dwSize]; + if (pTable == NULL) + return NULL; + + memset (pTable, 0, dwSize); + + // allocate the initial handle segment + pTable->pSegmentList = SegmentAlloc(pTable); + + // if that failed then we are also out of business + if (!pTable->pSegmentList) + { + // free the table's memory and get out + delete [] (uint8_t*)pTable; + return NULL; + } + + // initialize the table's lock + // We need to allow CRST_UNSAFE_SAMELEVEL, because + // during AD unload, we need to move some TableSegment from unloaded domain to default domain. + // We need to take both locks for the two HandleTable's to avoid racing with concurrent gc thread. + if (!pTable->Lock.InitNoThrow(CrstHandleTable, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_UNSAFE_SAMELEVEL))) + { + SegmentFree(pTable->pSegmentList); + delete [] (uint8_t*)pTable; + return NULL; + } + + // remember how many types we are supporting + pTable->uTypeCount = uTypeCount; + + // Store user data + pTable->uTableIndex = (uint32_t) -1; + pTable->uADIndex = uADIndex; + + // loop over various arrays an initialize them + uint32_t u; + + // initialize the type flags for the types we were passed + for (u = 0; u < uTypeCount; u++) + pTable->rgTypeFlags[u] = pTypeFlags[u]; + + // preinit the rest to HNDF_NORMAL + while (u < HANDLE_MAX_INTERNAL_TYPES) + pTable->rgTypeFlags[u++] = HNDF_NORMAL; + + // initialize the main cache + for (u = 0; u < uTypeCount; u++) + { + // at init time, the only non-zero field in a type cache is the free index + pTable->rgMainCache[u].lFreeIndex = HANDLES_PER_CACHE_BANK; + } + +#ifdef _DEBUG + // set up scanning stats + pTable->_DEBUG_iMaxGen = -1; +#endif + + // all done - return the newly created table + return (HHANDLETABLE)pTable; +} + + +/* + * HndDestroyHandleTable + * + * Cleans up and frees the specified handle table. + * + */ +void HndDestroyHandleTable(HHANDLETABLE hTable) +{ + WRAPPER_NO_CONTRACT; + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + // decrement handle count by number of handles in this table + COUNTER_ONLY(GetPerfCounters().m_GC.cHandles -= HndCountHandles(hTable)); + + // We are going to free the memory for this HandleTable. + // Let us reset the copy in g_pHandleTableArray to NULL. + // Otherwise, GC will think this HandleTable is still available. + + // free the lock + pTable->Lock.Destroy(); + + // fetch the segment list and null out the list pointer + TableSegment *pSegment = pTable->pSegmentList; + pTable->pSegmentList = NULL; + + // walk the segment list, freeing the segments as we go + while (pSegment) + { + // fetch the next segment + TableSegment *pNextSegment = pSegment->pNextSegment; + + // free the current one and advance to the next + SegmentFree(pSegment); + pSegment = pNextSegment; + } + + // free the table's memory + delete [] (uint8_t*) pTable; +} +/* + * HndSetHandleTableIndex + * + * Sets the index associated with a handle table at creation + */ +void HndSetHandleTableIndex(HHANDLETABLE hTable, uint32_t uTableIndex) +{ + WRAPPER_NO_CONTRACT; + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + pTable->uTableIndex = uTableIndex; +} +#endif // !DACCESS_COMPILE + +/* + * HndGetHandleTableIndex + * + * Retrieves the index associated with a handle table at creation + */ +uint32_t HndGetHandleTableIndex(HHANDLETABLE hTable) +{ + WRAPPER_NO_CONTRACT; + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + _ASSERTE (pTable->uTableIndex != (uint32_t) -1); // We have not set uTableIndex yet. + return pTable->uTableIndex; +} + +/* + * HndGetHandleTableIndex + * + * Retrieves the AppDomain index associated with a handle table at creation + */ +ADIndex HndGetHandleTableADIndex(HHANDLETABLE hTable) +{ + WRAPPER_NO_CONTRACT; + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + return pTable->uADIndex; +} + +/* + * HndGetHandleTableIndex + * + * Retrieves the AppDomain index associated with a handle table at creation + */ +ADIndex HndGetHandleADIndex(OBJECTHANDLE handle) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + // fetch the handle table pointer + HandleTable *pTable = Table(HndGetHandleTable(handle)); + + return pTable->uADIndex; +} + +#ifndef DACCESS_COMPILE +/* + * HndCreateHandle + * + * Entrypoint for allocating an individual handle. + * + */ +OBJECTHANDLE HndCreateHandle(HHANDLETABLE hTable, uint32_t uType, OBJECTREF object, uintptr_t lExtraInfo) +{ + CONTRACTL + { +#ifdef FEATURE_REDHAWK + // Redhawk returns NULL on failure. + NOTHROW; +#else + THROWS; +#endif + GC_NOTRIGGER; + if (object != NULL) + { + MODE_COOPERATIVE; + } + else + { + MODE_ANY; + } + SO_INTOLERANT; + } + CONTRACTL_END; + +#if defined( _DEBUG) && !defined(FEATURE_REDHAWK) + if (g_pConfig->ShouldInjectFault(INJECTFAULT_HANDLETABLE)) + { + FAULT_NOT_FATAL(); + char *a = new char; + delete a; + } +#endif // _DEBUG && !FEATURE_REDHAWK + + VALIDATEOBJECTREF(object); + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + // sanity check the type index + _ASSERTE(uType < pTable->uTypeCount); + + // get a handle from the table's cache + OBJECTHANDLE handle = TableAllocSingleHandleFromCache(pTable, uType); + + // did the allocation succeed? + if (!handle) + { +#ifdef FEATURE_REDHAWK + return NULL; +#else + ThrowOutOfMemory(); +#endif + } + +#ifdef DEBUG_DestroyedHandleValue + if (*(_UNCHECKED_OBJECTREF *)handle == DEBUG_DestroyedHandleValue) + *(_UNCHECKED_OBJECTREF *)handle = NULL; +#endif + + // yep - the handle better not point at anything yet + _ASSERTE(*(_UNCHECKED_OBJECTREF *)handle == NULL); + + // we are not holding the lock - check to see if there is nonzero extra info + if (lExtraInfo) + { + // initialize the user data BEFORE assigning the referent + // this ensures proper behavior if we are currently scanning + HandleQuickSetUserData(handle, lExtraInfo); + } + + // store the reference + HndAssignHandle(handle, object); + +#if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE) + g_dwHandles++; +#endif // ENABLE_PERF_COUNTERS || FEATURE_EVENT_TRACE + +#ifdef GC_PROFILING + { + BEGIN_PIN_PROFILER(CORProfilerTrackGC()); + g_profControlBlock.pProfInterface->HandleCreated((uintptr_t)handle, (ObjectID)OBJECTREF_TO_UNCHECKED_OBJECTREF(object)); + END_PIN_PROFILER(); + } +#endif //GC_PROFILING + + STRESS_LOG2(LF_GC, LL_INFO1000, "CreateHandle: %p, type=%d\n", handle, uType); + + // return the result + return handle; +} +#endif // !DACCESS_COMPILE + +#ifdef _DEBUG +void ValidateFetchObjrefForHandle(OBJECTREF objref, ADIndex appDomainIndex) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_SO_TOLERANT; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_DEBUG_ONLY; + + BEGIN_DEBUG_ONLY_CODE; + VALIDATEOBJECTREF (objref); + + AppDomain *pDomain = SystemDomain::GetAppDomainAtIndex(appDomainIndex); + + // Access to a handle in unloaded domain is not allowed + _ASSERTE(pDomain != NULL); + _ASSERTE(!pDomain->NoAccessToHandleTable()); + +#if CHECK_APP_DOMAIN_LEAKS + if (g_pConfig->AppDomainLeaks() && objref != NULL) + { + if (appDomainIndex.m_dwIndex) + objref->TryAssignAppDomain(pDomain); + else + objref->TrySetAppDomainAgile(); + } +#endif + END_DEBUG_ONLY_CODE; +} + +void ValidateAssignObjrefForHandle(OBJECTREF objref, ADIndex appDomainIndex) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_SO_TOLERANT; + STATIC_CONTRACT_MODE_COOPERATIVE; + STATIC_CONTRACT_DEBUG_ONLY; + + BEGIN_DEBUG_ONLY_CODE; + + VALIDATEOBJECTREF (objref); + + AppDomain *pDomain = SystemDomain::GetAppDomainAtIndex(appDomainIndex); + + // Access to a handle in unloaded domain is not allowed + _ASSERTE(pDomain != NULL); + _ASSERTE(!pDomain->NoAccessToHandleTable()); + +#if CHECK_APP_DOMAIN_LEAKS + if (g_pConfig->AppDomainLeaks() && objref != NULL) + { + if (appDomainIndex.m_dwIndex) + objref->TryAssignAppDomain(pDomain); + else + objref->TrySetAppDomainAgile(); + } +#endif + END_DEBUG_ONLY_CODE; +} + +void ValidateAppDomainForHandle(OBJECTHANDLE handle) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + +#ifdef DEBUG_DestroyedHandleValue + // Verify that we are not trying to access freed handle. + _ASSERTE("Attempt to access destroyed handle." && *(_UNCHECKED_OBJECTREF *)handle != DEBUG_DestroyedHandleValue); +#endif +#ifdef DACCESS_COMPILE + UNREFERENCED_PARAMETER(handle); +#else + BEGIN_DEBUG_ONLY_CODE; + ADIndex id = HndGetHandleADIndex(handle); + AppDomain *pUnloadingDomain = SystemDomain::AppDomainBeingUnloaded(); + if (!pUnloadingDomain || pUnloadingDomain->GetIndex() != id) + { + return; + } + if (!pUnloadingDomain->NoAccessToHandleTable()) + { + return; + } + _ASSERTE (!"Access to a handle in unloaded domain is not allowed"); + END_DEBUG_ONLY_CODE; +#endif // !DACCESS_COMPILE +} +#endif + + +#ifndef DACCESS_COMPILE +/* + * HndDestroyHandle + * + * Entrypoint for freeing an individual handle. + * + */ +void HndDestroyHandle(HHANDLETABLE hTable, uint32_t uType, OBJECTHANDLE handle) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + CAN_TAKE_LOCK; // because of TableFreeSingleHandleToCache + } + CONTRACTL_END; + + STRESS_LOG2(LF_GC, LL_INFO1000, "DestroyHandle: *%p->%p\n", handle, *(_UNCHECKED_OBJECTREF *)handle); + + FireEtwDestroyGCHandle((void*) handle, GetClrInstanceId()); + FireEtwPrvDestroyGCHandle((void*) handle, GetClrInstanceId()); + + // sanity check handle we are being asked to free + _ASSERTE(handle); + +#ifdef _DEBUG + ValidateAppDomainForHandle(handle); +#endif + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + +#ifdef GC_PROFILING + { + BEGIN_PIN_PROFILER(CORProfilerTrackGC()); + g_profControlBlock.pProfInterface->HandleDestroyed((uintptr_t)handle); + END_PIN_PROFILER(); + } +#endif //GC_PROFILING + +#if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE) + g_dwHandles--; +#endif // ENABLE_PERF_COUNTERS || FEATURE_EVENT_TRACE + + // sanity check the type index + _ASSERTE(uType < pTable->uTypeCount); + + _ASSERTE(HandleFetchType(handle) == uType); + + // return the handle to the table's cache + TableFreeSingleHandleToCache(pTable, uType, handle); +} + + +/* + * HndDestroyHandleOfUnknownType + * + * Entrypoint for freeing an individual handle whose type is unknown. + * + */ +void HndDestroyHandleOfUnknownType(HHANDLETABLE hTable, OBJECTHANDLE handle) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + + // sanity check handle we are being asked to free + _ASSERTE(handle); + +#ifdef FEATURE_COMINTEROP + // If we're being asked to destroy a WinRT weak handle, that will cause a leak + // of the IWeakReference* that it holds in its extra data. Instead of using this + // API use DestroyWinRTWeakHandle instead. + _ASSERTE(HandleFetchType(handle) != HNDTYPE_WEAK_WINRT); +#endif // FEATURE_COMINTEROP + + // fetch the type and then free normally + HndDestroyHandle(hTable, HandleFetchType(handle), handle); +} + + +/* + * HndCreateHandles + * + * Entrypoint for allocating handles in bulk. + * + */ +uint32_t HndCreateHandles(HHANDLETABLE hTable, uint32_t uType, OBJECTHANDLE *pHandles, uint32_t uCount) +{ + WRAPPER_NO_CONTRACT; + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + // sanity check the type index + _ASSERTE(uType < pTable->uTypeCount); + + // keep track of the number of handles we've allocated + uint32_t uSatisfied = 0; + + // if this is a large number of handles then bypass the cache + if (uCount > SMALL_ALLOC_COUNT) + { + CrstHolder ch(&pTable->Lock); + + // allocate handles in bulk from the main handle table + uSatisfied = TableAllocBulkHandles(pTable, uType, pHandles, uCount); + } + + // do we still need to get some handles? + if (uSatisfied < uCount) + { + // get some handles from the cache + uSatisfied += TableAllocHandlesFromCache(pTable, uType, pHandles + uSatisfied, uCount - uSatisfied); + } + +#if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE) + g_dwHandles += uSatisfied; +#endif // ENABLE_PERF_COUNTERS || FEATURE_EVENT_TRACE + +#ifdef GC_PROFILING + { + BEGIN_PIN_PROFILER(CORProfilerTrackGC()); + for (uint32_t i = 0; i < uSatisfied; i++) + g_profControlBlock.pProfInterface->HandleCreated((uintptr_t)pHandles[i], 0); + END_PIN_PROFILER(); + } +#endif //GC_PROFILING + + // return the number of handles we allocated + return uSatisfied; +} + + +/* + * HndDestroyHandles + * + * Entrypoint for freeing handles in bulk. + * + */ +void HndDestroyHandles(HHANDLETABLE hTable, uint32_t uType, const OBJECTHANDLE *pHandles, uint32_t uCount) +{ + WRAPPER_NO_CONTRACT; + +#ifdef _DEBUG + ValidateAppDomainForHandle(pHandles[0]); +#endif + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + // sanity check the type index + _ASSERTE(uType < pTable->uTypeCount); + +#ifdef GC_PROFILING + { + BEGIN_PIN_PROFILER(CORProfilerTrackGC()); + for (uint32_t i = 0; i < uCount; i++) + g_profControlBlock.pProfInterface->HandleDestroyed((uintptr_t)pHandles[i]); + END_PIN_PROFILER(); + } +#endif + +#if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE) + g_dwHandles -= uCount; +#endif // ENABLE_PERF_COUNTERS || FEATURE_EVENT_TRACE + + // is this a small number of handles? + if (uCount <= SMALL_ALLOC_COUNT) + { + // yes - free them via the handle cache + TableFreeHandlesToCache(pTable, uType, pHandles, uCount); + return; + } + + // acquire the handle manager lock + { + CrstHolder ch(&pTable->Lock); + + // free the unsorted handles in bulk to the main handle table + TableFreeBulkUnpreparedHandles(pTable, uType, pHandles, uCount); + } +} + +/* + * HndSetHandleExtraInfo + * + * Stores owner data with handle. + * + */ +void HndSetHandleExtraInfo(OBJECTHANDLE handle, uint32_t uType, uintptr_t lExtraInfo) +{ + WRAPPER_NO_CONTRACT; + + // fetch the user data slot for this handle if we have the right type + uintptr_t *pUserData = HandleValidateAndFetchUserDataPointer(handle, uType); + + // is there a slot? + if (pUserData) + { + // yes - store the info + *pUserData = lExtraInfo; + } +} + +/* +* HndCompareExchangeHandleExtraInfo +* +* Stores owner data with handle. +* +*/ +uintptr_t HndCompareExchangeHandleExtraInfo(OBJECTHANDLE handle, uint32_t uType, uintptr_t lOldExtraInfo, uintptr_t lNewExtraInfo) +{ + WRAPPER_NO_CONTRACT; + + // fetch the user data slot for this handle if we have the right type + uintptr_t *pUserData = HandleValidateAndFetchUserDataPointer(handle, uType); + + // is there a slot? + if (pUserData) + { + // yes - attempt to store the info + return (uintptr_t)Interlocked::CompareExchangePointer((void**)pUserData, (void*)lNewExtraInfo, (void*)lOldExtraInfo); + } + + _ASSERTE(!"Shouldn't be trying to call HndCompareExchangeHandleExtraInfo on handle types without extra info"); + return NULL; +} +#endif // !DACCESS_COMPILE + +/* + * HndGetHandleExtraInfo + * + * Retrieves owner data from handle. + * + */ +uintptr_t HndGetHandleExtraInfo(OBJECTHANDLE handle) +{ + WRAPPER_NO_CONTRACT; + + // assume zero until we actually get it + uintptr_t lExtraInfo = 0L; + + // fetch the user data slot for this handle + PTR_uintptr_t pUserData = HandleQuickFetchUserDataPointer(handle); + + // if we did then copy the value + if (pUserData) + { + lExtraInfo = *(pUserData); + } + + // return the value to our caller + return lExtraInfo; +} + +/* + * HndGetHandleTable + * + * Returns the containing table of a handle. + * + */ +HHANDLETABLE HndGetHandleTable(OBJECTHANDLE handle) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + PTR_HandleTable pTable = HandleFetchHandleTable(handle); + + return (HHANDLETABLE)pTable; +} + +void HndLogSetEvent(OBJECTHANDLE handle, _UNCHECKED_OBJECTREF value) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_SO_TOLERANT; + STATIC_CONTRACT_MODE_COOPERATIVE; + +#if !defined(DACCESS_COMPILE) && defined(FEATURE_EVENT_TRACE) + if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, SetGCHandle) || + ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context, SetGCHandle)) + { + uint32_t hndType = HandleFetchType(handle); + ADIndex appDomainIndex = HndGetHandleADIndex(handle); + AppDomain* pAppDomain = SystemDomain::GetAppDomainAtIndex(appDomainIndex); + uint32_t generation = value != 0 ? GCHeap::GetGCHeap()->WhichGeneration(value) : 0; + FireEtwSetGCHandle((void*) handle, value, hndType, generation, (int64_t) pAppDomain, GetClrInstanceId()); + FireEtwPrvSetGCHandle((void*) handle, value, hndType, generation, (int64_t) pAppDomain, GetClrInstanceId()); + +#ifndef FEATURE_REDHAWK + // Also fire the things pinned by Async pinned handles + if (hndType == HNDTYPE_ASYNCPINNED) + { + if (value->GetMethodTable() == g_pOverlappedDataClass) + { + OverlappedDataObject* overlapped = (OverlappedDataObject*) value; + if (overlapped->m_isArray) + { + ArrayBase* pUserObject = (ArrayBase*)OBJECTREFToObject(overlapped->m_userObject); + Object **ppObj = (Object**)pUserObject->GetDataPtr(TRUE); + size_t num = pUserObject->GetNumComponents(); + for (size_t i = 0; i < num; i ++) + { + value = ppObj[i]; + uint32_t generation = value != 0 ? GCHeap::GetGCHeap()->WhichGeneration(value) : 0; + FireEtwSetGCHandle(overlapped, value, HNDTYPE_PINNED, generation, (int64_t) pAppDomain, GetClrInstanceId()); + } + } + else + { + value = OBJECTREF_TO_UNCHECKED_OBJECTREF(overlapped->m_userObject); + uint32_t generation = value != 0 ? GCHeap::GetGCHeap()->WhichGeneration(value) : 0; + FireEtwSetGCHandle(overlapped, value, HNDTYPE_PINNED, generation, (int64_t) pAppDomain, GetClrInstanceId()); + } + } + } +#endif // FEATURE_REDHAWK + } +#else + UNREFERENCED_PARAMETER(handle); + UNREFERENCED_PARAMETER(value); +#endif +} + +/* + * HndWriteBarrier + * + * Resets the generation number for the handle's clump to zero. + * + */ +void HndWriteBarrier(OBJECTHANDLE handle, OBJECTREF objref) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_SO_TOLERANT; + STATIC_CONTRACT_MODE_COOPERATIVE; + + // unwrap the objectref we were given + _UNCHECKED_OBJECTREF value = OBJECTREF_TO_UNCHECKED_OBJECTREF(objref); + + _ASSERTE (objref != NULL); + + // find the write barrier for this handle + uint8_t *barrier = (uint8_t *)((uintptr_t)handle & HANDLE_SEGMENT_ALIGN_MASK); + + // sanity + _ASSERTE(barrier); + + // find the offset of this handle into the segment + uintptr_t offset = (uintptr_t)handle & HANDLE_SEGMENT_CONTENT_MASK; + + // make sure it is in the handle area and not the header + _ASSERTE(offset >= HANDLE_HEADER_SIZE); + + // compute the clump index for this handle + offset = (offset - HANDLE_HEADER_SIZE) / (HANDLE_SIZE * HANDLE_HANDLES_PER_CLUMP); + + // Be careful to read and write the age byte via volatile operations. Otherwise the compiler has been + // observed to translate the read + conditional write sequence below into an unconditional read/write + // (utilizing a conditional register move to determine whether the write is an update or simply writes + // back what was read). This is a legal transformation for non-volatile accesses but obviously leads to a + // race condition where we can lose an update (see the comment below for the race condition). + volatile uint8_t * pClumpAge = barrier + offset; + + // if this age is smaller than age of the clump, update the clump age + if (*pClumpAge != 0) // Perf optimization: if clumpAge is 0, nothing more to do + { + // find out generation + int generation = GCHeap::GetGCHeap()->WhichGeneration(value); + uint32_t uType = HandleFetchType(handle); + +#ifndef FEATURE_REDHAWK + //OverlappedData need special treatment: because all user data pointed by it needs to be reported by this handle, + //its age is consider to be min age of the user data, to be simple, we just make it 0 + if (uType == HNDTYPE_ASYNCPINNED && objref->GetGCSafeMethodTable () == g_pOverlappedDataClass) + { + generation = 0; + } +#endif // !FEATURE_REDHAWK + + if (uType == HNDTYPE_DEPENDENT) + { + generation = 0; + } + + if (*pClumpAge > (uint8_t) generation) + { + // We have to be careful here. HndWriteBarrier is not under any synchronization + // Consider the scenario where 2 threads are hitting the line below at the same + // time. Only one will win. If the winner has an older age than the loser, we + // just created a potential GC hole (The clump will not be reporting the + // youngest handle in the clump, thus GC may skip the clump). To fix this + // we just set the clump age to 0, which means that whoever wins the race + // results are the same, as GC will always look at the clump + *pClumpAge = (uint8_t)0; + } + } +} + +/* + * HndEnumHandles + * + * Enumerates all handles of the specified type in the handle table. + * + * This entrypoint is provided for utility code (debugger support etc) that + * needs to enumerate all roots in the handle table. + * + */ +void HndEnumHandles(HHANDLETABLE hTable, const uint32_t *puType, uint32_t uTypeCount, + HANDLESCANPROC pfnEnum, uintptr_t lParam1, uintptr_t lParam2, bool fAsync) +{ + WRAPPER_NO_CONTRACT; + + // fetch the handle table pointer + PTR_HandleTable pTable = Table(hTable); + + // per-block scanning callback + BLOCKSCANPROC pfnBlock; + + // do we need to support user data? + BOOL fEnumUserData = TypesRequireUserDataScanning(pTable, puType, uTypeCount); + + if (fEnumUserData) + { + // scan all handles with user data + pfnBlock = BlockScanBlocksWithUserData; + } + else + { + // scan all handles without user data + pfnBlock = BlockScanBlocksWithoutUserData; + } + + // set up parameters for handle enumeration + ScanCallbackInfo info; + + info.uFlags = (fAsync? HNDGCF_ASYNC : HNDGCF_NORMAL); + info.fEnumUserData = fEnumUserData; + info.dwAgeMask = 0; + info.pCurrentSegment = NULL; + info.pfnScan = pfnEnum; + info.param1 = lParam1; + info.param2 = lParam2; + + // choose a scanning method based on the async flag + TABLESCANPROC pfnScanTable = TableScanHandles; + if (fAsync) + pfnScanTable = xxxTableScanHandlesAsync; + + { + // acquire the handle manager lock + CrstHolderWithState ch(&pTable->Lock); + + // scan the table + pfnScanTable(pTable, puType, uTypeCount, FullSegmentIterator, pfnBlock, &info, &ch); + } +} + +/* + * HndScanHandlesForGC + * + * Multiple type scanning entrypoint for GC. + * + * This entrypoint is provided for GC-time scnas of the handle table ONLY. It + * enables ephemeral scanning of the table, and optionally ages the write barrier + * as it scans. + * + */ +void HndScanHandlesForGC(HHANDLETABLE hTable, HANDLESCANPROC scanProc, uintptr_t param1, uintptr_t param2, + const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, uint32_t flags) +{ + WRAPPER_NO_CONTRACT; + + // fetch the table pointer + PTR_HandleTable pTable = Table(hTable); + + // per-segment and per-block callbacks + SEGMENTITERATOR pfnSegment; + BLOCKSCANPROC pfnBlock = NULL; + + // do we need to support user data? + BOOL enumUserData = + ((flags & HNDGCF_EXTRAINFO) && + TypesRequireUserDataScanning(pTable, types, typeCount)); + + // what type of GC are we performing? + if (condemned >= maxgen) + { + // full GC - use our full-service segment iterator + pfnSegment = FullSegmentIterator; + + // see if there is a callback + if (scanProc) + { + // do we need to scan blocks with user data? + if (enumUserData) + { + // scan all with user data + pfnBlock = BlockScanBlocksWithUserData; + } + else + { + // scan all without user data + pfnBlock = BlockScanBlocksWithoutUserData; + } + } + else if (flags & HNDGCF_AGE) + { + // there is only aging to do + pfnBlock = BlockAgeBlocks; + } + } + else + { + // this is an ephemeral GC - is it g0? + if (condemned == 0) + { + // yes - do bare-bones enumeration + pfnSegment = QuickSegmentIterator; + } + else + { + // no - do normal enumeration + pfnSegment = StandardSegmentIterator; + } + + // see if there is a callback + if (scanProc) + { + // there is a scan callback - scan the condemned generation + pfnBlock = BlockScanBlocksEphemeral; + } +#ifndef DACCESS_COMPILE + else if (flags & HNDGCF_AGE) + { + // there is only aging to do + pfnBlock = BlockAgeBlocksEphemeral; + } +#endif + } + + // set up parameters for scan callbacks + ScanCallbackInfo info; + + info.uFlags = flags; + info.fEnumUserData = enumUserData; + info.dwAgeMask = BuildAgeMask(condemned, maxgen); + info.pCurrentSegment = NULL; + info.pfnScan = scanProc; + info.param1 = param1; + info.param2 = param2; + +#if defined(_DEBUG) && !defined(DACCESS_COMPILE) + info.DEBUG_BlocksScanned = 0; + info.DEBUG_BlocksScannedNonTrivially = 0; + info.DEBUG_HandleSlotsScanned = 0; + info.DEBUG_HandlesActuallyScanned = 0; +#endif + + // choose a scanning method based on the async flag + TABLESCANPROC pfnScanTable = TableScanHandles; + if (flags & HNDGCF_ASYNC) + { + pfnScanTable = xxxTableScanHandlesAsync; + } + + { + // lock the table down for concurrent GC only + CrstHolderWithState ch(&pTable->Lock, (flags & HNDGCF_ASYNC) != 0); + + // perform the scan + pfnScanTable(pTable, types, typeCount, pfnSegment, pfnBlock, &info, &ch); + +#if defined(_DEBUG) && !defined(DACCESS_COMPILE) + // update our scanning statistics for this generation + DEBUG_PostGCScanHandler(pTable, types, typeCount, condemned, maxgen, &info); + #endif + } +} + +#ifndef DACCESS_COMPILE + + +/* + * HndResetAgeMap + * + * Service to forceably reset the age map for a set of handles. + * + * Provided for GC-time resetting the handle table's write barrier. This is not + * normally advisable, as it increases the amount of work that will be done in + * subsequent scans. Under some circumstances, however, this is precisely what is + * desired. Generally this entrypoint should only be used under some exceptional + * condition during garbage collection, like objects being demoted from a higher + * generation to a lower one. + * + */ +void HndResetAgeMap(HHANDLETABLE hTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, uint32_t flags) +{ + WRAPPER_NO_CONTRACT; + + // fetch the table pointer + HandleTable *pTable = Table(hTable); + + // set up parameters for scan callbacks + ScanCallbackInfo info; + + info.uFlags = flags; + info.fEnumUserData = FALSE; + info.dwAgeMask = BuildAgeMask(condemned, maxgen); + info.pCurrentSegment = NULL; + info.pfnScan = NULL; + info.param1 = 0; + info.param2 = 0; + + { + // lock the table down + CrstHolderWithState ch(&pTable->Lock); + + // perform the scan + TableScanHandles(pTable, types, typeCount, QuickSegmentIterator, BlockResetAgeMapForBlocks, &info, &ch); + } +} + + +/* + * HndVerifyTable + * + * Service to check the correctness of the handle table for a set of handles + * + * Provided for checking the correctness of handle table and the gc. + * Will validate that each handle points to a valid object. + * Will also validate that the generation of the handle is <= generation of the object. + * Cannot have == because the handle table only remembers the generation for a group of + * 16 handles. + * + */ +void HndVerifyTable(HHANDLETABLE hTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, uint32_t flags) +{ + WRAPPER_NO_CONTRACT; + + // fetch the table pointer + HandleTable *pTable = Table(hTable); + + // set up parameters for scan callbacks + ScanCallbackInfo info; + + info.uFlags = flags; + info.fEnumUserData = FALSE; + info.dwAgeMask = BuildAgeMask(condemned, maxgen); + info.pCurrentSegment = NULL; + info.pfnScan = NULL; + info.param1 = 0; + info.param2 = 0; + + { + // lock the table down + CrstHolderWithState ch(&pTable->Lock); + + // perform the scan + TableScanHandles(pTable, types, typeCount, QuickSegmentIterator, BlockVerifyAgeMapForBlocks, &info, &ch); + } +} + + +/* + * HndNotifyGcCycleComplete + * + * Informs the handle table that a GC has completed. + * + */ +void HndNotifyGcCycleComplete(HHANDLETABLE hTable, uint32_t condemned, uint32_t maxgen) +{ +#ifdef _DEBUG + WRAPPER_NO_CONTRACT; + + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + { + // lock the table down + CrstHolder ch(&pTable->Lock); + + // if this was a full GC then dump a cumulative log of scanning stats + if (condemned >= maxgen) + DEBUG_LogScanningStatistics(pTable, LL_INFO10); + } +#else + LIMITED_METHOD_CONTRACT; + UNREFERENCED_PARAMETER(hTable); + UNREFERENCED_PARAMETER(condemned); + UNREFERENCED_PARAMETER(maxgen); +#endif +} + +extern int getNumberOfSlots(); + + +/* + * HndCountHandles + * + * Counts the number of handles owned by the handle table that are marked as + * "used" that are not currently residing in the handle table's cache. + * + * Provided to compute the correct value for the GC Handle perfcounter. + * The caller is responsible for acquiring the handle table's lock if + * it is necessary. + * + */ +uint32_t HndCountHandles(HHANDLETABLE hTable) +{ + WRAPPER_NO_CONTRACT; + // fetch the handle table pointer + HandleTable *pTable = Table(hTable); + + // initialize the count of handles in the cache to 0 + uint32_t uCacheCount = 0; + + // fetch the count of handles marked as "used" + uint32_t uCount = pTable->dwCount; + + // loop through the main cache for each handle type + HandleTypeCache *pCache = pTable->rgMainCache; + HandleTypeCache *pCacheEnd = pCache + pTable->uTypeCount; + for (; pCache != pCacheEnd; ++pCache) + { + // get relevant indexes for the reserve bank and the free bank + int32_t lFreeIndex = pCache->lFreeIndex; + int32_t lReserveIndex = pCache->lReserveIndex; + + // clamp the min free index and min reserve index to be non-negative; + // this is necessary since interlocked operations can set these variables + // to negative values, and once negative they stay negative until the + // cache is rebalanced + if (lFreeIndex < 0) lFreeIndex = 0; + if (lReserveIndex < 0) lReserveIndex = 0; + + // compute the number of handles + uint32_t uHandleCount = (uint32_t)lReserveIndex + (HANDLES_PER_CACHE_BANK - (uint32_t)lFreeIndex); + + // add the number of handles to the total handle count and update + // dwCount in this HandleTable + uCacheCount += uHandleCount; + } + + // it is not necessary to have the lock while reading the quick cache; + // loop through the quick cache for each handle type + OBJECTHANDLE * pQuickCache = pTable->rgQuickCache; + OBJECTHANDLE * pQuickCacheEnd = pQuickCache + HANDLE_MAX_INTERNAL_TYPES; + for (; pQuickCache != pQuickCacheEnd; ++pQuickCache) + if (*pQuickCache) + ++uCacheCount; + + // return the number of handles marked as "used" that are not + // residing in the cache + return (uCount - uCacheCount); +} + + +/* + * HndCountAllHandles + * + * Counts the total number of handles that are marked as "used" that are not + * currently residing in some handle table's cache. + * + * Provided to compute the correct value for the GC Handle perfcounter. + * The 'fUseLocks' flag specifies whether to acquire each handle table's lock + * while its handles are being counted. + * + */ +uint32_t HndCountAllHandles(BOOL fUseLocks) +{ + uint32_t uCount = 0; + int offset = 0; + + // get number of HandleTables per HandleTableBucket + int n_slots = getNumberOfSlots(); + + // fetch the pointer to the head of the list + struct HandleTableMap * walk = &g_HandleTableMap; + + // walk the list + while (walk) + { + int nextOffset = walk->dwMaxIndex; + int max = nextOffset - offset; + PTR_PTR_HandleTableBucket pBucket = walk->pBuckets; + PTR_PTR_HandleTableBucket pLastBucket = pBucket + max; + + // loop through each slot in this node + for (; pBucket != pLastBucket; ++pBucket) + { + // if there is a HandleTableBucket in this slot + if (*pBucket) + { + // loop through the HandleTables inside this HandleTableBucket, + // and accumulate the handle count of each HandleTable + HHANDLETABLE * pTable = (*pBucket)->pTable; + HHANDLETABLE * pLastTable = pTable + n_slots; + + // if the 'fUseLocks' flag is set, acquire the lock for this handle table before + // calling HndCountHandles() - this will prevent dwCount from being modified and + // it will also prevent any of the main caches from being rebalanced + if (fUseLocks) + for (; pTable != pLastTable; ++pTable) + { + CrstHolder ch(&(Table(*pTable)->Lock)); + uCount += HndCountHandles(*pTable); + } + else + for (; pTable != pLastTable; ++pTable) + uCount += HndCountHandles(*pTable); + } + } + + offset = nextOffset; + walk = walk->pNext; + } + + //return the total number of handles in all HandleTables + return uCount; +} + +#ifndef FEATURE_REDHAWK +BOOL Ref_HandleAsyncPinHandles() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + if (GetThread()) {MODE_COOPERATIVE;} else {DISABLED(MODE_COOPERATIVE);} + } + CONTRACTL_END; + + HandleTableBucket *pBucket = g_HandleTableMap.pBuckets[0]; + BOOL result = FALSE; + int limit = getNumberOfSlots(); + for (int n = 0; n < limit; n ++ ) + { + if (TableHandleAsyncPinHandles(Table(pBucket->pTable[n]))) + { + result = TRUE; + } + } + + return result; +} + +void Ref_RelocateAsyncPinHandles(HandleTableBucket *pSource, HandleTableBucket *pTarget) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + int limit = getNumberOfSlots(); + for (int n = 0; n < limit; n ++ ) + { + TableRelocateAsyncPinHandles(Table(pSource->pTable[n]), Table(pTarget->pTable[n])); + } +} +#endif // !FEATURE_REDHAWK + +BOOL Ref_ContainHandle(HandleTableBucket *pBucket, OBJECTHANDLE handle) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + int limit = getNumberOfSlots(); + for (int n = 0; n < limit; n ++ ) + { + if (TableContainHandle(Table(pBucket->pTable[n]), handle)) + return TRUE; + } + + return FALSE; +} +/*--------------------------------------------------------------------------*/ + + + +/**************************************************************************** + * + * DEBUG SCANNING STATISTICS + * + ****************************************************************************/ +#ifdef _DEBUG + +void DEBUG_PostGCScanHandler(HandleTable *pTable, const uint32_t *types, uint32_t typeCount, uint32_t condemned, uint32_t maxgen, ScanCallbackInfo *info) +{ + LIMITED_METHOD_CONTRACT; + UNREFERENCED_PARAMETER(types); + + // looks like the GC supports more generations than we expected + _ASSERTE(condemned < MAXSTATGEN); + + // remember the highest generation we've seen + if (pTable->_DEBUG_iMaxGen < (int)condemned) + pTable->_DEBUG_iMaxGen = (int)condemned; + + // update the statistics + pTable->_DEBUG_TotalBlocksScanned [condemned] += info->DEBUG_BlocksScanned; + pTable->_DEBUG_TotalBlocksScannedNonTrivially [condemned] += info->DEBUG_BlocksScannedNonTrivially; + pTable->_DEBUG_TotalHandleSlotsScanned [condemned] += info->DEBUG_HandleSlotsScanned; + pTable->_DEBUG_TotalHandlesActuallyScanned [condemned] += info->DEBUG_HandlesActuallyScanned; + + // if this is an ephemeral GC then dump ephemeral stats for this scan right now + if (condemned < maxgen) + { + // dump a header for the stats with the condemned generation number + LOG((LF_GC, LL_INFO1000, "--------------------------------------------------------------\n")); + LOG((LF_GC, LL_INFO1000, "Ephemeral Handle Scan Summary:\n")); + LOG((LF_GC, LL_INFO1000, " Generation = %u\n", condemned)); + + // dump the handle types we were asked to scan + LOG((LF_GC, LL_INFO1000, " Handle Type(s) = %u", *types)); + for (uint32_t u = 1; u < typeCount; u++) + LOG((LF_GC, LL_INFO1000, ",%u", types[u])); + LOG((LF_GC, LL_INFO1000, "\n")); + + // dump the number of blocks and slots we scanned + uint32_t blockHandles = info->DEBUG_BlocksScanned * HANDLE_HANDLES_PER_BLOCK; + LOG((LF_GC, LL_INFO1000, " Blocks Scanned = %u (%u slots)\n", info->DEBUG_BlocksScanned, blockHandles)); + + // if we scanned any blocks then summarize some stats + if (blockHandles) + { + uint32_t nonTrivialBlockHandles = info->DEBUG_BlocksScannedNonTrivially * HANDLE_HANDLES_PER_BLOCK; + LOG((LF_GC, LL_INFO1000, " Blocks Examined = %u (%u slots)\n", info->DEBUG_BlocksScannedNonTrivially, nonTrivialBlockHandles)); + + LOG((LF_GC, LL_INFO1000, " Slots Scanned = %u\n", info->DEBUG_HandleSlotsScanned)); + LOG((LF_GC, LL_INFO1000, " Handles Scanned = %u\n", info->DEBUG_HandlesActuallyScanned)); + + double scanRatio = ((double)info->DEBUG_HandlesActuallyScanned / (double)blockHandles) * 100.0; + + LOG((LF_GC, LL_INFO1000, " Handle Scanning Ratio = %1.1lf%%\n", scanRatio)); + } + + // dump a footer for the stats + LOG((LF_GC, LL_INFO1000, "--------------------------------------------------------------\n")); + } +} + +void DEBUG_LogScanningStatistics(HandleTable *pTable, uint32_t level) +{ + WRAPPER_NO_CONTRACT; + UNREFERENCED_PARAMETER(level); + + // have we done any GC's yet? + if (pTable->_DEBUG_iMaxGen >= 0) + { + // dump a header for the stats + LOG((LF_GC, level, "\n==============================================================\n")); + LOG((LF_GC, level, " Cumulative Handle Scan Summary:\n")); + + // for each generation we've collected, dump the current stats + for (int i = 0; i <= pTable->_DEBUG_iMaxGen; i++) + { + int64_t totalBlocksScanned = pTable->_DEBUG_TotalBlocksScanned[i]; + + // dump the generation number and the number of blocks scanned + LOG((LF_GC, level, "--------------------------------------------------------------\n")); + LOG((LF_GC, level, " Condemned Generation = %d\n", i)); + LOG((LF_GC, level, " Blocks Scanned = %I64u\n", totalBlocksScanned)); + + // if we scanned any blocks in this generation then dump some interesting numbers + if (totalBlocksScanned) + { + LOG((LF_GC, level, " Blocks Examined = %I64u\n", pTable->_DEBUG_TotalBlocksScannedNonTrivially[i])); + LOG((LF_GC, level, " Slots Scanned = %I64u\n", pTable->_DEBUG_TotalHandleSlotsScanned [i])); + LOG((LF_GC, level, " Handles Scanned = %I64u\n", pTable->_DEBUG_TotalHandlesActuallyScanned [i])); + + double blocksScanned = (double) totalBlocksScanned; + double blocksExamined = (double) pTable->_DEBUG_TotalBlocksScannedNonTrivially[i]; + double slotsScanned = (double) pTable->_DEBUG_TotalHandleSlotsScanned [i]; + double handlesScanned = (double) pTable->_DEBUG_TotalHandlesActuallyScanned [i]; + double totalSlots = (double) (totalBlocksScanned * HANDLE_HANDLES_PER_BLOCK); + + LOG((LF_GC, level, " Block Scan Ratio = %1.1lf%%\n", (100.0 * (blocksExamined / blocksScanned)) )); + LOG((LF_GC, level, " Clump Scan Ratio = %1.1lf%%\n", (100.0 * (slotsScanned / totalSlots)) )); + LOG((LF_GC, level, " Scanned Clump Saturation = %1.1lf%%\n", (100.0 * (handlesScanned / slotsScanned)) )); + LOG((LF_GC, level, " Overall Handle Scan Ratio = %1.1lf%%\n", (100.0 * (handlesScanned / totalSlots)) )); + } + } + + // dump a footer for the stats + LOG((LF_GC, level, "==============================================================\n\n")); + } +} + +#endif // _DEBUG +#endif // !DACCESS_COMPILE + + +/*--------------------------------------------------------------------------*/ + + |