diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 02cf2917aa54e6..6bca1c321c8901 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -116,7 +116,8 @@ public readonly struct GCOomData HandleType[] GetHandleTypes(uint[] types); // Gets the extra info (user data) associated with a dependent handle TargetNUInt GetHandleExtraInfo(TargetPointer handle); - // Gets the global allocation context pointer and limit + // Gets the global allocation context pointer and limit. Both are null when the target + // runtime does not allocate out of a global allocation context. void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit); // Gets handle table memory regions (segments) @@ -293,7 +294,7 @@ public readonly record struct GCHeapSegmentInfo( | `GCHighestAddress` | `pointer` | Highest GC address as recorded by the VM/GC interface | | `GCIdentifiers` | `string` | CSV string containing identifiers of the GC. Current values are "server", "workstation", "regions", and "segments" | | `GCLowestAddress` | `pointer` | Lowest GC address as recorded by the VM/GC interface | -| `GlobalAllocContext` | `pointer` | Pointer to the global EEAllocContext | +| `GlobalAllocContext` | `pointer` | Pointer to the global EEAllocContext. Only available in runtimes that allocate out of a global allocation context instead of thread allocation contexts. | | `GlobalFreeHugeRegions` | `pointer` | Pointer to the global free huge region list | | `GlobalMechanismsLength` | `uint32` | Number of counters in the global GC mechanisms array | | `GlobalRegionsToDecommit` | `pointer` | Pointer to the global regions-to-decommit array | @@ -881,7 +882,15 @@ GetGlobalAllocationContext ```csharp void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) { - TargetPointer globalAllocContextAddress = target.ReadGlobalPointer("GlobalAllocContext"); + // "GlobalAllocContext" is optional: runtimes which only allocate out of thread allocation + // contexts do not define it. Report an empty context in that case. + if (!target.TryReadGlobalPointer("GlobalAllocContext", out TargetPointer? globalAllocContextAddress)) + { + allocPtr = TargetPointer.Null; + allocLimit = TargetPointer.Null; + return; + } + allocPtr = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Pointer offset */); allocLimit = target.ReadPointer(globalAllocContextAddress + /* EEAllocContext::GCAllocationContext offset */ + /* GCAllocContext::Limit offset */); } @@ -1149,10 +1158,11 @@ IEnumerable<(HeapSegment Segment, TargetPointer Address)> WalkSegmentList(Target GetPotentialNextObjectAddress Computes the next candidate object address when walking a Gen0/Ephemeral segment. -Active allocation contexts (per-thread, the global non-thread-local context, and -the per-heap Gen0 context) carve out reserved-but-not-yet-allocated ranges inside -such segments; when the naive `current + size` lands on one of those ranges the -walk must skip past it. The contexts are collected via `IThread.GetThreadStoreData` +Active allocation contexts (per-thread, the global non-thread-local context when the +target runtime has one, and the per-heap Gen0 context) carve out +reserved-but-not-yet-allocated ranges inside such segments; when the naive +`current + size` lands on one of those ranges the walk must skip past it. The +contexts are collected via `IThread.GetThreadStoreData` and `IThread.GetThreadData` (per-thread contexts), `IGC.GetGlobalAllocationContext` (global context), and `IGC.GetGCIdentifiers` + `IGC.GetGCHeaps` + `IGC.GetHeapData` (per-heap Gen0 contexts). diff --git a/docs/design/datacontracts/data-descriptor-meanings.json b/docs/design/datacontracts/data-descriptor-meanings.json index 2188d62962bdf8..850b0196acbd48 100644 --- a/docs/design/datacontracts/data-descriptor-meanings.json +++ b/docs/design/datacontracts/data-descriptor-meanings.json @@ -781,7 +781,7 @@ "GCLowestAddress": "Lowest GC address as recorded by the VM/GC interface", "GcNotificationFlags": "Global flag for storing GC notification data", "GCThread": "Pointer to the GC thread", - "GlobalAllocContext": "Pointer to the global EEAllocContext", + "GlobalAllocContext": "Pointer to the global EEAllocContext. Only available in runtimes that allocate out of a global allocation context instead of thread allocation contexts.", "GlobalFreeHugeRegions": "Pointer to the global free huge region list", "GlobalMechanismsLength": "Number of counters in the global GC mechanisms array", "GlobalRegionsToDecommit": "Pointer to the global regions-to-decommit array", diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 1de344cd271f3c..30f214e136e43b 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -6641,7 +6641,7 @@ HRESULT DacHeapWalker::Init(CORDB_ADDRESS start, CORDB_ADDRESS end) if (threadStore != NULL) { int count = (int)threadStore->ThreadCountInEE(); - mAllocInfo = new (nothrow) AllocInfo[count + 1]; + mAllocInfo = new (nothrow) AllocInfo[count]; if (mAllocInfo == NULL) return E_OUTOFMEMORY; @@ -6668,13 +6668,6 @@ HRESULT DacHeapWalker::Init(CORDB_ADDRESS start, CORDB_ADDRESS end) j++; } } - gc_alloc_context globalCtx = ((ee_alloc_context)g_global_alloc_context).m_GCAllocContext; - if (globalCtx.alloc_ptr != nullptr) - { - mAllocInfo[j].Ptr = (CORDB_ADDRESS)globalCtx.alloc_ptr; - mAllocInfo[j].Limit = (CORDB_ADDRESS)globalCtx.alloc_limit; - j++; - } mAllocContextCount = j; } diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index a206ea3db232ea..8df3746c6a8c9f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -5498,9 +5498,10 @@ HRESULT ClrDataAccess::GetGlobalAllocationContext( } SOSDacEnter(); - gc_alloc_context global_alloc_context = ((ee_alloc_context)g_global_alloc_context).m_GCAllocContext; - *allocPtr = (CLRDATA_ADDRESS)global_alloc_context.alloc_ptr; - *allocLimit = (CLRDATA_ADDRESS)global_alloc_context.alloc_limit; + // The runtime does not allocate out of a global allocation context - every allocation goes + // through a thread allocation context. Report an empty context. + *allocPtr = (CLRDATA_ADDRESS)0; + *allocLimit = (CLRDATA_ADDRESS)0; SOSDacLeave(); return hr; } diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index d4303963fc69db..d6badce75704c0 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -262,13 +262,6 @@ RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_HeapVerify, W("HeapVerify"), 0, "When set v RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCCpuGroup, W("GCCpuGroup"), 0, "Specifies if to enable GC to support CPU groups") RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCName, W("GCName"), "") RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCPath, W("GCPath"), "") -/** - * This flag allows us to force the runtime to use global allocation context on Windows x86/amd64 instead of thread allocation context just for testing purpose. - * The flag is unsafe for a subtle reason. Although the access to the g_global_alloc_context is protected under a lock. The implementation of - * that lock in the JIT helpers are not multi-core safe (in particular, it used and inc instruction without using the LOCK prefix). This is - * only useful for ad-hoc testing. - */ -CONFIG_DWORD_INFO(INTERNAL_GCUseGlobalAllocationContext, W("GCUseGlobalAllocationContext"), 0, "Force using the global allocation context for testing only") /// /// JIT diff --git a/src/coreclr/inc/dacvars.h b/src/coreclr/inc/dacvars.h index 600b0e9c553c30..1dd3ecbc4c5103 100644 --- a/src/coreclr/inc/dacvars.h +++ b/src/coreclr/inc/dacvars.h @@ -150,7 +150,6 @@ DEFINE_DACVAR(ProfControlBlock, dac__g_profControlBlock, ::g_profControlBlock) DEFINE_DACVAR(PTR_DWORD, dac__g_card_table, ::g_card_table) DEFINE_DACVAR(PTR_BYTE, dac__g_lowest_address, ::g_lowest_address) DEFINE_DACVAR(PTR_BYTE, dac__g_highest_address, ::g_highest_address) -DEFINE_DACVAR(ee_alloc_context, dac__g_global_alloc_context, ::g_global_alloc_context) DEFINE_DACVAR(IGCHeap, dac__g_pGCHeap, ::g_pGCHeap) diff --git a/src/coreclr/vm/amd64/AllocSlow.asm b/src/coreclr/vm/amd64/AllocSlow.asm index fbe8876ee13d56..af89bb58666596 100644 --- a/src/coreclr/vm/amd64/AllocSlow.asm +++ b/src/coreclr/vm/amd64/AllocSlow.asm @@ -9,9 +9,6 @@ EXTERN RhpNewVariableSizeObject : PROC EXTERN RhpGcAllocMaybeFrozen : PROC EXTERN RhExceptionHandling_FailedAllocation_Helper : PROC -EXTERN g_global_alloc_lock : DWORD -EXTERN g_global_alloc_context : QWORD - ; ; Object* RhpNew(MethodTable *pMT) ; @@ -71,175 +68,4 @@ NESTED_ENTRY RhExceptionHandling_FailedAllocation, _TEXT NESTED_END RhExceptionHandling_FailedAllocation, _TEXT -; -; void RhpNewFast_UP(MethodTable *pMT) -; -; Allocate non-array object, uniprocessor version -; -LEAF_ENTRY RhpNewFast_UP, _TEXT - - inc [g_global_alloc_lock] - jnz RhpNewFast_UP_RarePath - - ;; - ;; rcx contains MethodTable pointer - ;; - mov r8d, [rcx + OFFSETOF__MethodTable__m_uBaseSize] - - ;; - ;; eax: base size - ;; rcx: MethodTable pointer - ;; rdx: ee_alloc_context pointer - ;; - - mov rax, [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr] - add r8, rax - cmp r8, [g_global_alloc_context + OFFSETOF__ee_alloc_context__combined_limit] - ja RhpNewFast_UP_RarePath_Unlock - - ;; set the new alloc pointer - mov [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr], r8 - - ;; set the new object's MethodTable pointer - mov [rax], rcx - mov [g_global_alloc_lock], -1 - ret - -RhpNewFast_UP_RarePath_Unlock: - mov [g_global_alloc_lock], -1 - -RhpNewFast_UP_RarePath: - xor edx, edx - jmp RhpNewObject - -LEAF_END RhpNewFast_UP, _TEXT - -; -; Shared code for RhNewString_UP, RhpNewArrayFast_UP and RhpNewPtrArrayFast_UP -; RAX == string/array size -; RCX == MethodTable -; RDX == character/element count -; -NEW_ARRAY_FAST_UP MACRO - - inc [g_global_alloc_lock] - jnz RhpNewVariableSizeObject - - mov r8, rax - add rax, [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr] - jc NewArrayFast_RarePath - - ; rax == new alloc ptr - ; rcx == MethodTable - ; rdx == element count - ; r8 == array size - cmp rax, [g_global_alloc_context + OFFSETOF__ee_alloc_context__combined_limit] - ja NewArrayFast_RarePath - - mov [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr], rax - - ; calc the new object pointer - sub rax, r8 - - mov [rax + OFFSETOF__Object__m_pEEType], rcx - mov [rax + OFFSETOF__Array__m_Length], edx - mov [g_global_alloc_lock], -1 - ret - -NewArrayFast_RarePath: - mov [g_global_alloc_lock], -1 - jmp RhpNewVariableSizeObject - -ENDM - -; -; Object* RhNewString_UP(MethodTable *pMT, DWORD stringLength) -; -; Allocate a string, uniprocessor version -; -LEAF_ENTRY RhNewString_UP, _TEXT - - ; we want to limit the element count to the non-negative 32-bit int range - cmp rdx, MAX_STRING_LENGTH - ja StringSizeOverflow - - ; Compute overall allocation size (align(base size + (element size * elements), 8)). - lea rax, [(rdx * STRING_COMPONENT_SIZE) + (STRING_BASE_SIZE + 7)] - and rax, -8 - - NEW_ARRAY_FAST_UP - -StringSizeOverflow: - ; We get here if the size of the final string object can't be represented as an unsigned - ; 32-bit value. We're going to tail-call to a managed helper that will throw - ; an OOM exception that the caller of this allocator understands. - - ; rcx holds MethodTable pointer already - xor edx, edx ; Indicate that we should throw OOM. - jmp RhExceptionHandling_FailedAllocation - -LEAF_END RhNewString_UP, _TEXT - -; -; Object* RhpNewArrayFast_UP(MethodTable *pMT, INT_PTR elementCount) -; Object* RhpNewArrayFast_UP_OBJ(MethodTable *pMT, INT_PTR elementCount) -; -; Allocate one dimensional, zero based array (SZARRAY), uniprocessor version -; -LEAF_ENTRY RhpNewArrayFast_UP, _TEXT - - ; we want to limit the element count to the non-negative 32-bit int range - cmp rdx, 07fffffffh - ja ArraySizeOverflow - - ; save element count - mov r8, rdx - - ; Compute overall allocation size (align(base size + (element size * elements), 8)). - movzx eax, word ptr [rcx + OFFSETOF__MethodTable__m_usComponentSize] - imul rax, rdx - lea rax, [rax + SZARRAY_BASE_SIZE + 7] - and rax, -8 - - mov rdx, r8 - - NEW_ARRAY_FAST_UP - -ArraySizeOverflow: - ; We get here if the size of the final array object can't be represented as an unsigned - ; 32-bit value. We're going to tail-call to a managed helper that will throw - ; an overflow exception that the caller of this allocator understands. - - ; rcx holds MethodTable pointer already - mov edx, 1 ; Indicate that we should throw OverflowException - jmp RhExceptionHandling_FailedAllocation - -LEAF_END RhpNewArrayFast_UP, _TEXT - -; -; Object* RhpNewPtrArrayFast_UP(MethodTable *pMT, INT_PTR elementCount) -; -; Allocate one dimensional, zero based array (SZARRAY) of pointer sized elements, -; uniprocessor version -; -LEAF_ENTRY RhpNewPtrArrayFast_UP, _TEXT - - ; Delegate overflow handling to the generic helper conservatively - - cmp rdx, (40000000h / 8) ; sizeof(void*) - jae RhpNewVariableSizeObject - - ; In this case we know the element size is sizeof(void *), or 8 for x64 - ; This helps us in two ways - we can shift instead of multiplying, and - ; there's no need to align the size either - - lea eax, [edx * 8 + SZARRAY_BASE_SIZE] - - ; No need for rounding in this case - element size is 8, and m_BaseSize is guaranteed - ; to be a multiple of 8. - - NEW_ARRAY_FAST_UP - -LEAF_END RhpNewPtrArrayFast_UP, _TEXT - end diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index d9e25e04380be1..f7a09d9745158d 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1797,7 +1797,6 @@ CDAC_GLOBAL_POINTER(ComWrappersVtablePtrs, InteropLib::ABI::g_knownQueryInterfac CDAC_GLOBAL_POINTER(GcNotificationFlags, &::g_gcNotificationFlags) CDAC_GLOBAL_POINTER(JITNotificationTable, &::g_pNotificationTable) CDAC_GLOBAL(JITNotificationTableSize, T_UINT32, JIT_NOTIFICATION_TABLE_SIZE) -CDAC_GLOBAL_POINTER(GlobalAllocContext, &::g_global_alloc_context) CDAC_GLOBAL_POINTER(CoreLib, &::g_CoreLib) #ifdef TARGET_WINDOWS CDAC_GLOBAL_POINTER(TlsIndexBase, &::_tls_index) diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp index 62bcffa98ad674..63f3440f5efbe8 100644 --- a/src/coreclr/vm/gccover.cpp +++ b/src/coreclr/vm/gccover.cpp @@ -887,9 +887,6 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) // Do the actual stress work // - // BUG(github #10318) - when not using allocation contexts, the alloc lock - // must be acquired here. Until fixed, this assert prevents random heap corruption. - assert(GCHeapUtilities::UseThreadAllocationContexts()); GCHeapUtilities::GetGCHeap()->StressHeap(&t_runtime_thread_locals.alloc_context.m_GCAllocContext); // StressHeap can exit early w/o forcing a SuspendEE to trigger the instruction update @@ -1195,9 +1192,6 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion) // Do the actual stress work // - // BUG(github #10318)- when not using allocation contexts, the alloc lock - // must be acquired here. Until fixed, this assert prevents random heap corruption. - assert(GCHeapUtilities::UseThreadAllocationContexts()); GCHeapUtilities::GetGCHeap()->StressHeap(&t_runtime_thread_locals.alloc_context.m_GCAllocContext); // StressHeap can exit early w/o forcing a SuspendEE to trigger the instruction update diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 14f7572a09c4f3..541b478acb1173 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -495,35 +495,24 @@ void GCToEEInterface::GcEnumAllocContexts(enum_alloc_context_func* fn, void* par } CONTRACTL_END; - if (GCHeapUtilities::UseThreadAllocationContexts()) + Thread * pThread = NULL; + while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) { - Thread * pThread = NULL; - while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL) + ee_alloc_context* palloc_context = pThread->GetEEAllocContext(); + if (palloc_context != nullptr) { - ee_alloc_context* palloc_context = pThread->GetEEAllocContext(); - if (palloc_context != nullptr) + gc_alloc_context* ac = &palloc_context->m_GCAllocContext; + fn(ac, param); + // The GC may zero the alloc_ptr and alloc_limit fields of AC during enumeration and we need to keep + // m_CombinedLimit up-to-date. Note that the GC has multiple threads running this enumeration concurrently + // with no synchronization. If you need to change this code think carefully about how that concurrency + // may affect the results. + if (ac->alloc_limit == 0 && palloc_context->m_CombinedLimit != 0) { - gc_alloc_context* ac = &palloc_context->m_GCAllocContext; - fn(ac, param); - // The GC may zero the alloc_ptr and alloc_limit fields of AC during enumeration and we need to keep - // m_CombinedLimit up-to-date. Note that the GC has multiple threads running this enumeration concurrently - // with no synchronization. If you need to change this code think carefully about how that concurrency - // may affect the results. - if (ac->alloc_limit == 0 && palloc_context->m_CombinedLimit != 0) - { - palloc_context->m_CombinedLimit = 0; - } + palloc_context->m_CombinedLimit = 0; } } } - else - { - fn(&g_global_alloc_context.m_GCAllocContext, param); - if (g_global_alloc_context.m_GCAllocContext.alloc_limit == 0 && g_global_alloc_context.m_CombinedLimit != 0) - { - g_global_alloc_context.m_CombinedLimit = 0; - } - } } diff --git a/src/coreclr/vm/gcheaputilities.cpp b/src/coreclr/vm/gcheaputilities.cpp index e86508ab207fab..aa1a5c201966eb 100644 --- a/src/coreclr/vm/gcheaputilities.cpp +++ b/src/coreclr/vm/gcheaputilities.cpp @@ -41,8 +41,6 @@ bool g_sw_ww_enabled_for_gc_heap = false; #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP -GVAL_IMPL_INIT(ee_alloc_context, g_global_alloc_context, {}); - thread_local ee_alloc_context::PerThreadRandom ee_alloc_context::t_random = PerThreadRandom(); enum GC_LOAD_STATUS { @@ -66,8 +64,6 @@ VersionInfo g_gc_version_info; // The module that contains the GC. PTR_VOID g_gc_module_base; -bool GCHeapUtilities::s_useThreadAllocationContexts; - // GC entrypoints for the linked-in GC. These symbols are invoked // directly if we are not using a standalone GC. extern "C" void LOCALGC_CALLCONV GC_VersionInfo(/* Out */ VersionInfo* info); @@ -371,19 +367,6 @@ HRESULT GCHeapUtilities::LoadAndInitialize() { LIMITED_METHOD_CONTRACT; - // When running on a single-proc Intel system, it's more efficient to use a single global - // allocation context for SOH allocations than to use one for every thread. -#if (defined(TARGET_X86) || defined(TARGET_AMD64)) && !defined(TARGET_UNIX) -#if DEBUG - bool useGlobalAllocationContext = (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_GCUseGlobalAllocationContext) != 0); -#else - bool useGlobalAllocationContext = false; -#endif - s_useThreadAllocationContexts = !useGlobalAllocationContext && (IsServerHeap() || ::g_SystemInfo.dwNumberOfProcessors != 1 || CPUGroupInfo::CanEnableGCCPUGroups()); -#else - s_useThreadAllocationContexts = true; -#endif - // we should only call this once on startup. Attempting to load a GC // twice is an error. assert(g_pGCHeap == nullptr); diff --git a/src/coreclr/vm/gcheaputilities.h b/src/coreclr/vm/gcheaputilities.h index 7215d0ce189c3b..8636354d5e13e5 100644 --- a/src/coreclr/vm/gcheaputilities.h +++ b/src/coreclr/vm/gcheaputilities.h @@ -147,12 +147,6 @@ GPTR_DECL(uint8_t,g_highest_address); GPTR_DECL(uint32_t,g_card_table); GVAL_DECL(GCHeapType, g_heap_type); -// For single-proc machines, the EE will use a single, shared alloc context -// for all allocations. In order to avoid extra indirections in assembly -// allocation helpers, the EE owns the global allocation context and the -// GC will update it when it needs to. -GVAL_DECL(ee_alloc_context, g_global_alloc_context); - #ifndef DACCESS_COMPILE } #endif // !DACCESS_COMPILE @@ -254,15 +248,6 @@ class GCHeapUtilities { #endif // FEATURE_SVR_GC } - static bool UseThreadAllocationContexts() - { -#if (defined(TARGET_X86) || defined(TARGET_AMD64)) && !defined(TARGET_UNIX) - return s_useThreadAllocationContexts; -#else - return true; -#endif - } - #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP // Returns True if software write watch is currently enabled for the GC Heap, @@ -344,8 +329,6 @@ class GCHeapUtilities { private: // This class should never be instantiated. GCHeapUtilities() = delete; - - static bool s_useThreadAllocationContexts; }; #endif // _GCHEAPUTILITIES_H_ diff --git a/src/coreclr/vm/gchelpers.cpp b/src/coreclr/vm/gchelpers.cpp index 1751ce3273b62f..24147a2ec1d7c9 100644 --- a/src/coreclr/vm/gchelpers.cpp +++ b/src/coreclr/vm/gchelpers.cpp @@ -45,8 +45,6 @@ EXTERN_C ee_alloc_context* GetThreadEEAllocContext() { WRAPPER_NO_CONTRACT; - assert(GCHeapUtilities::UseThreadAllocationContexts()); - return &t_runtime_thread_locals.alloc_context; } @@ -201,105 +199,6 @@ EXTERN_C void RhExceptionHandling_FailedAllocation_Helper(MethodTable* pMT, bool pFrame->Pop(CURRENT_THREAD); } -// When not using per-thread allocation contexts, we (the EE) need to take care that -// no two threads are concurrently modifying the global allocation context. This lock -// must be acquired before any sort of operations involving the global allocation context -// can occur. -// -// This lock is acquired by all allocations when not using per-thread allocation contexts. -// It is acquired in two kinds of places: -// 1) JIT_TrialAllocFastSP (and related assembly alloc helpers), which attempt to -// acquire it but move into an alloc slow path if acquiring fails -// (but does not decrement the lock variable when doing so) -// 2) Alloc in gchelpers.cpp, which acquire the lock using -// the Acquire and Release methods below. -class GlobalAllocLock { - friend struct AsmOffsets; -private: - // The lock variable. This field must always be first. - LONG m_lock; - -public: - // Creates a new GlobalAllocLock in the unlocked state. - GlobalAllocLock() : m_lock(-1) {} - - // Copy and copy-assignment operators should never be invoked - // for this type - GlobalAllocLock(const GlobalAllocLock&) = delete; - GlobalAllocLock& operator=(const GlobalAllocLock&) = delete; - - // Acquires the lock, spinning if necessary to do so. When this method - // returns, m_lock will be zero and the lock will be acquired. - void Acquire() - { - CONTRACTL { - NOTHROW; - GC_TRIGGERS; // switch to preemptive mode - MODE_COOPERATIVE; - } CONTRACTL_END; - - DWORD spinCount = 0; - while(InterlockedExchange(&m_lock, 0) != -1) - { - GCX_PREEMP(); - __SwitchToThread(0, spinCount++); - } - - assert(m_lock == 0); - } - - // Releases the lock. - void Release() - { - LIMITED_METHOD_CONTRACT; - - // the lock may not be exactly 0. This is because the - // assembly alloc routines increment the lock variable and - // jump if not zero to the slow alloc path, which eventually - // will try to acquire the lock again. At that point, it will - // spin in Acquire (since m_lock is some number that's not zero). - // When the thread that /does/ hold the lock releases it, the spinning - // thread will continue. - MemoryBarrier(); - assert(m_lock >= 0); - m_lock = -1; - } - - // Static helper to acquire a lock, for use with the Holder template. - static void AcquireLock(GlobalAllocLock *lock) - { - WRAPPER_NO_CONTRACT; - lock->Acquire(); - } - - // Static helper to release a lock, for use with the Holder template - static void ReleaseLock(GlobalAllocLock *lock) - { - WRAPPER_NO_CONTRACT; - lock->Release(); - } - - typedef class Holder Holder; -}; - -typedef GlobalAllocLock::Holder GlobalAllocLockHolder; - -struct AsmOffsets { - static_assert(offsetof(GlobalAllocLock, m_lock) == 0, "ASM code relies on this property"); -}; - -// For single-proc machines, the global allocation context is protected -// from concurrent modification by this lock. -// -// When not using per-thread allocation contexts, certain methods on IGCHeap -// require that this lock be held before calling. These methods are documented -// on the IGCHeap interface. -extern "C" -{ - GlobalAllocLock g_global_alloc_lock; -} - - // Checks to see if the given allocation size exceeds the // largest object size allowed - if it does, it throws // an OutOfMemoryException with a message indicating that @@ -475,21 +374,10 @@ inline Object* Alloc(size_t size, GC_ALLOC_FLAGS flags) Object *retVal = NULL; CheckObjectSize(size); - if (GCHeapUtilities::UseThreadAllocationContexts()) - { - ee_alloc_context *threadContext = GetThreadEEAllocContext(); - CdacStress::MaybeVerify(); - GCStress::MaybeTrigger(&threadContext->m_GCAllocContext); - retVal = Alloc(threadContext, size, flags); - } - else - { - GlobalAllocLockHolder holder(&g_global_alloc_lock); - ee_alloc_context *globalContext = &g_global_alloc_context; - CdacStress::MaybeVerify(); - GCStress::MaybeTrigger(&globalContext->m_GCAllocContext); - retVal = Alloc(globalContext, size, flags); - } + ee_alloc_context *threadContext = GetThreadEEAllocContext(); + CdacStress::MaybeVerify(); + GCStress::MaybeTrigger(&threadContext->m_GCAllocContext); + retVal = Alloc(threadContext, size, flags); if (!retVal) diff --git a/src/coreclr/vm/gcstress.h b/src/coreclr/vm/gcstress.h index b1eccb02c56097..55199d67f0c736 100644 --- a/src/coreclr/vm/gcstress.h +++ b/src/coreclr/vm/gcstress.h @@ -295,9 +295,6 @@ namespace _GCStress FORCEINLINE static void Trigger() { - // BUG(github #10318) - when not using allocation contexts, the alloc lock - // must be acquired here. Until fixed, this assert prevents random heap corruption. - _ASSERTE(GCHeapUtilities::UseThreadAllocationContexts()); GCHeapUtilities::GetGCHeap()->StressHeap(&t_runtime_thread_locals.alloc_context.m_GCAllocContext); } diff --git a/src/coreclr/vm/i386/AllocSlow.asm b/src/coreclr/vm/i386/AllocSlow.asm index a8f37b88506829..1a846526493d8f 100644 --- a/src/coreclr/vm/i386/AllocSlow.asm +++ b/src/coreclr/vm/i386/AllocSlow.asm @@ -14,12 +14,6 @@ EXTERN _RhExceptionHandling_FailedAllocation_Helper@12 : PROC EXTERN @RhpNewObject@8 : PROC EXTERN @RhpNewVariableSizeObject@8 : PROC -g_global_alloc_lock EQU _g_global_alloc_lock -g_global_alloc_context EQU _g_global_alloc_context - -EXTERN g_global_alloc_lock : DWORD -EXTERN g_global_alloc_context : DWORD - ; ; Object* RhpNew(MethodTable *pMT) ; @@ -79,196 +73,4 @@ RhExceptionHandling_FailedAllocation PROC PUBLIC ret RhExceptionHandling_FailedAllocation ENDP -; -; void RhpNewFast_UP(MethodTable *pMT) -; -; Allocate non-array object, uniprocessor version -; -FASTCALL_FUNC RhpNewFast_UP, 4 - inc [g_global_alloc_lock] - jnz AllocFailed - - mov eax, [ecx + OFFSETOF__MethodTable__m_uBaseSize] - add eax, [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr] - jc AllocFailed_Unlock - cmp eax, [g_global_alloc_context + OFFSETOF__ee_alloc_context__combined_limit] - ja AllocFailed_Unlock - mov [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr], eax - - ; calc the new object pointer and initialize it - sub eax, [ecx + OFFSETOF__MethodTable__m_uBaseSize] - mov [eax + OFFSETOF__Object__m_pEEType], ecx - - mov [g_global_alloc_lock], -1 - ret - -AllocFailed_Unlock: - mov [g_global_alloc_lock], -1 - -AllocFailed: - xor edx, edx - jmp @RhpNewObject@8 -FASTCALL_ENDFUNC - -; -; Shared code for RhNewString_UP, RhpNewArrayFast_UP and RhpNewPtrArrayFast_UP -; EAX == string/array size -; ECX == MethodTable -; EDX == character/element count -; -NEW_ARRAY_FAST_PROLOG_UP MACRO - inc [g_global_alloc_lock] - jnz @RhpNewVariableSizeObject@8 - - push ecx - push edx -ENDM - -NEW_ARRAY_FAST_UP MACRO - LOCAL AllocContextOverflow - - ; ECX == MethodTable - ; EAX == allocation size - ; EDX == string length - - mov ecx, eax - add eax, [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr] - jc AllocContextOverflow - cmp eax, [g_global_alloc_context + OFFSETOF__ee_alloc_context__combined_limit] - ja AllocContextOverflow - - ; ECX == allocation size - ; EAX == new alloc ptr - - ; set the new alloc pointer - mov [g_global_alloc_context + OFFSETOF__ee_alloc_context__alloc_ptr], eax - - ; calc the new object pointer - sub eax, ecx - - ; Restore the element count and put it in edx - pop edx - ; Restore the MethodTable and put it in ecx - pop ecx - - ; set the new object's MethodTable pointer and element count - mov [eax + OFFSETOF__Object__m_pEEType], ecx - mov [eax + OFFSETOF__Array__m_Length], edx - mov [g_global_alloc_lock], -1 - ret - -AllocContextOverflow: - ; Restore the element count and put it in edx - pop edx - ; Restore the MethodTable and put it in ecx - pop ecx - - mov [g_global_alloc_lock], -1 - jmp @RhpNewVariableSizeObject@8 -ENDM - -; -; Object* RhNewString_UP(MethodTable *pMT, DWORD stringLength) -; -; Allocate a string, uniprocessor version -; -FASTCALL_FUNC RhNewString_UP, 8 - ;; Make sure computing the aligned overall allocation size won't overflow - cmp edx, MAX_STRING_LENGTH - ja StringSizeOverflow - - ; Compute overall allocation size (align(base size + (element size * elements), 4)). - lea eax, [(edx * STRING_COMPONENT_SIZE) + (STRING_BASE_SIZE + 3)] - and eax, -4 - - NEW_ARRAY_FAST_PROLOG_UP - NEW_ARRAY_FAST_UP - -StringSizeOverflow: - ;; We get here if the size of the final string object can't be represented as an unsigned - ;; 32-bit value. We're going to tail-call to a managed helper that will throw - ;; an OOM exception that the caller of this allocator understands. - - ;; ecx holds MethodTable pointer already - xor edx, edx ; Indicate that we should throw OOM. - jmp RhExceptionHandling_FailedAllocation -FASTCALL_ENDFUNC - -; -; Object* RhpNewArrayFast_UP(MethodTable *pMT, INT_PTR elementCount) -; -; Allocate one dimensional, zero based array (SZARRAY), uniprocessor version -; -FASTCALL_FUNC RhpNewArrayFast_UP, 8 - NEW_ARRAY_FAST_PROLOG_UP - - ; Compute overall allocation size (align(base size + (element size * elements), 4)). - ; if the element count is <= 0x10000, no overflow is possible because the component size is - ; <= 0xffff, and thus the product is <= 0xffff0000, and the base size for the worst case - ; (32 dimensional MdArray) is less than 0xffff. - movzx eax, word ptr [ecx + OFFSETOF__MethodTable__m_usComponentSize] - cmp edx,010000h - ja ArraySizeBig - mul edx - lea eax, [eax + SZARRAY_BASE_SIZE + 3] -ArrayAlignSize: - and eax, -4 - - NEW_ARRAY_FAST_UP - -ArraySizeBig: - ; Compute overall allocation size (align(base size + (element size * elements), 4)). - ; if the element count is negative, it's an overflow, otherwise it's out of memory - cmp edx, 0 - jl ArraySizeOverflow - mul edx - jc ArrayOutOfMemoryNoFrame - add eax, [ecx + OFFSETOF__MethodTable__m_uBaseSize] - jc ArrayOutOfMemoryNoFrame - add eax, 3 - jc ArrayOutOfMemoryNoFrame - jmp ArrayAlignSize - -ArrayOutOfMemoryNoFrame: - add esp, 8 - - ; ecx holds MethodTable pointer already - xor edx, edx ; Indicate that we should throw OOM. - jmp RhExceptionHandling_FailedAllocation - -ArraySizeOverflow: - add esp, 8 - - ; We get here if the size of the final array object can't be represented as an unsigned - ; 32-bit value. We're going to tail-call to a managed helper that will throw - ; an overflow exception that the caller of this allocator understands. - - ; ecx holds MethodTable pointer already - mov edx, 1 ; Indicate that we should throw OverflowException - jmp RhExceptionHandling_FailedAllocation -FASTCALL_ENDFUNC - -; -; Object* RhpNewPtrArrayFast_UP(MethodTable *pMT, INT_PTR elementCount) -; -; Allocate one dimensional, zero based array (SZARRAY) of pointer sized elements, -; uniprocessor version -; -FASTCALL_FUNC RhpNewPtrArrayFast_UP, 8 - ; Delegate overflow handling to the generic helper conservatively - - cmp edx, (40000000h / 4) ; sizeof(void*) - jae @RhpNewVariableSizeObject@8 - - ; In this case we know the element size is sizeof(void *), or 4 for x86 - ; This helps us in two ways - we can shift instead of multiplying, and - ; there's no need to align the size either - - lea eax, [edx * 4 + SZARRAY_BASE_SIZE] - - NEW_ARRAY_FAST_PROLOG_UP - NEW_ARRAY_FAST_UP -FASTCALL_ENDFUNC - - end diff --git a/src/coreclr/vm/i386/jitinterfacex86.cpp b/src/coreclr/vm/i386/jitinterfacex86.cpp index 8c6c4eabf988d2..75093192538673 100644 --- a/src/coreclr/vm/i386/jitinterfacex86.cpp +++ b/src/coreclr/vm/i386/jitinterfacex86.cpp @@ -28,8 +28,6 @@ #define WRITE_BARRIER_CHECK 1 #endif -extern "C" LONG g_global_alloc_lock; - extern "C" void STDCALL JIT_WriteBarrierReg_PreGrow();// JIThelp.asm/JIThelp.s extern "C" void STDCALL JIT_WriteBarrierReg_PostGrow();// JIThelp.asm/JIThelp.s diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 7727e7d8114832..54a58f15b0fe2b 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -160,13 +160,6 @@ EXTERN_C FCDECL1(Object*, RhpNewFastMisalign, MethodTable* pMT); EXTERN_C FCDECL2(Object*, RhpNewArrayFastAlign8, MethodTable* pMT, INT_PTR size); #endif -#if defined(TARGET_WINDOWS) && (defined(TARGET_AMD64) || defined(TARGET_X86)) -EXTERN_C FCDECL1(Object*, RhpNewFast_UP, MethodTable* pMT); -EXTERN_C FCDECL2(Object*, RhpNewArrayFast_UP, MethodTable* pMT, INT_PTR size); -EXTERN_C FCDECL2(Object*, RhpNewPtrArrayFast_UP, MethodTable* pMT, INT_PTR size); -EXTERN_C FCDECL2(Object*, RhNewString_UP, MethodTable* pMT, INT_PTR stringLength); -#endif - EXTERN_C FCDECL1(Object*, RhpNew, MethodTable* pMT); EXTERN_C FCDECL2(Object*, RhpNewVariableSizeObject, MethodTable* pMT, INT_PTR size); EXTERN_C FCDECL1(Object*, RhpNewMaybeFrozen, MethodTable* pMT); diff --git a/src/coreclr/vm/jitinterfacegen.cpp b/src/coreclr/vm/jitinterfacegen.cpp index 015a2e8dc7c75b..7d50747a56d688 100644 --- a/src/coreclr/vm/jitinterfacegen.cpp +++ b/src/coreclr/vm/jitinterfacegen.cpp @@ -33,42 +33,20 @@ void InitJITAllocationHelpers() { STANDARD_VM_CONTRACT; - _ASSERTE(g_SystemInfo.dwNumberOfProcessors != 0); - // Allocation helpers, faster but non-logging if (!(TrackAllocationsEnabled() || LoggingOn(LF_GCALLOC, LL_INFO10))) { - // if (multi-proc || server GC || non-Windows) - if (GCHeapUtilities::UseThreadAllocationContexts()) - { - SetJitHelperFunction(CORINFO_HELP_NEWSFAST, RhpNewFast); - SetJitHelperFunction(CORINFO_HELP_NEWARR_1_VC, RhpNewArrayFast); - SetJitHelperFunction(CORINFO_HELP_NEWARR_1_PTR, RhpNewPtrArrayFast); + SetJitHelperFunction(CORINFO_HELP_NEWSFAST, RhpNewFast); + SetJitHelperFunction(CORINFO_HELP_NEWARR_1_VC, RhpNewArrayFast); + SetJitHelperFunction(CORINFO_HELP_NEWARR_1_PTR, RhpNewPtrArrayFast); #if defined(FEATURE_64BIT_ALIGNMENT) - SetJitHelperFunction(CORINFO_HELP_NEWSFAST_ALIGN8, RhpNewFastAlign8); - SetJitHelperFunction(CORINFO_HELP_NEWSFAST_ALIGN8_VC, RhpNewFastMisalign); - SetJitHelperFunction(CORINFO_HELP_NEWARR_1_ALIGN8, RhpNewArrayFastAlign8); + SetJitHelperFunction(CORINFO_HELP_NEWSFAST_ALIGN8, RhpNewFastAlign8); + SetJitHelperFunction(CORINFO_HELP_NEWSFAST_ALIGN8_VC, RhpNewFastMisalign); + SetJitHelperFunction(CORINFO_HELP_NEWARR_1_ALIGN8, RhpNewArrayFastAlign8); #endif - ECall::DynamicallyAssignFCallImpl(GetEEFuncEntryPoint(RhNewString), ECall::FastAllocateString); - } - else - { -#if defined(TARGET_WINDOWS) && (defined(TARGET_AMD64) || defined(TARGET_X86)) - // Replace the 1p slow allocation helpers with faster version - // - // When we're running Workstation GC on a single proc box we don't have - // InlineGetThread versions because there is no need to call GetThread - SetJitHelperFunction(CORINFO_HELP_NEWSFAST, RhpNewFast_UP); - SetJitHelperFunction(CORINFO_HELP_NEWARR_1_VC, RhpNewArrayFast_UP); - SetJitHelperFunction(CORINFO_HELP_NEWARR_1_PTR, RhpNewPtrArrayFast_UP); - - ECall::DynamicallyAssignFCallImpl(GetEEFuncEntryPoint(RhNewString_UP), ECall::FastAllocateString); -#else - _ASSERTE(!"Expected to use ThreadAllocationContexts"); -#endif - } + ECall::DynamicallyAssignFCallImpl(GetEEFuncEntryPoint(RhNewString), ECall::FastAllocateString); } // Debugger depends on new helper names starting with CORINFO_HELP_NEW diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index b65134fd611157..8a8e7f73a9b828 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1162,11 +1162,6 @@ FCIMPL1(Object*, RuntimeTypeHandle::InternalAllocNoChecks_FastPath, MethodTable* _ASSERTE(pMT != nullptr); - if (!GCHeapUtilities::UseThreadAllocationContexts()) - { - return NULL; - } - if (pMT->HasFinalizer()) { return NULL; diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index ffa7a535611f9e..0a02c64b97be43 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -2326,9 +2326,6 @@ void Thread::PerformPreemptiveGC() GCX_COOP(); m_bGCStressing = TRUE; - // BUG(github #10318) - when not using allocation contexts, the alloc lock - // must be acquired here. Until fixed, this assert prevents random heap corruption. - _ASSERTE(GCHeapUtilities::UseThreadAllocationContexts()); GCHeapUtilities::GetGCHeap()->StressHeap(&t_runtime_thread_locals.alloc_context.m_GCAllocContext); m_bGCStressing = FALSE; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs index 92f215570618fa..22e7e5aceb65e5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs @@ -161,6 +161,8 @@ public interface IGC : IContract HandleType[] GetHandleTypes(uint[] types) => throw new NotImplementedException(); TargetNUInt GetHandleExtraInfo(TargetPointer handle) => throw new NotImplementedException(); + // Gets the global allocation context pointer and limit. Both are null when the target + // runtime does not allocate out of a global allocation context. void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) => throw new NotImplementedException(); IReadOnlyList GetHandleTableMemoryRegions() => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index 6b9370ace38d82..6071162bc91fd8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -292,8 +292,15 @@ private static GCOomData GetGCOomData(Data.OomHistory oomHistory) void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) { - TargetPointer globalAllocContextAddress = _target.ReadGlobalPointer(Constants.Globals.GlobalAllocContext); - Data.EEAllocContext eeAllocContext = _target.ProcessedData.GetOrAdd(globalAllocContextAddress); + // Runtimes which never allocate out of a global allocation context do not export the global. + if (!_target.TryReadGlobalPointer(Constants.Globals.GlobalAllocContext, out TargetPointer? globalAllocContextAddress)) + { + allocPtr = TargetPointer.Null; + allocLimit = TargetPointer.Null; + return; + } + + Data.EEAllocContext eeAllocContext = _target.ProcessedData.GetOrAdd(globalAllocContextAddress.Value); allocPtr = eeAllocContext.GCAllocationContext.Pointer; allocLimit = eeAllocContext.GCAllocationContext.Limit; } diff --git a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs index 733263f7001b21..d4f7c3ecc8146e 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -130,18 +130,17 @@ public void WorkstationGC_CanEnumerateExpectedHandles(TestConfiguration config) [ConditionalTheory] [MemberData(nameof(TestConfigurations))] [SkipOnVersion("net10.0", "GC contract is not available in .NET 10 dumps")] - public void WorkstationGC_GlobalAllocationContextIsReadable(TestConfiguration config) + public void WorkstationGC_GlobalAllocationContextIsEmpty(TestConfiguration config) { InitializeDumpTest(config); IGC gcContract = Target.Contracts.GC; + + // The runtime allocates exclusively out of thread allocation contexts and does not + // export the optional GlobalAllocContext global, so the contract reports an empty context. gcContract.GetGlobalAllocationContext(out TargetPointer pointer, out TargetPointer limit); - if (pointer != TargetPointer.Null) - { - Assert.NotEqual(TargetPointer.Null, limit); - Assert.True(pointer <= limit, - $"Expected allocPtr (0x{pointer:X}) <= allocLimit (0x{limit:X})"); - } + Assert.Equal(TargetPointer.Null, pointer); + Assert.Equal(TargetPointer.Null, limit); } [ConditionalTheory]