Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions docs/design/datacontracts/GC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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` | Optional. Pointer to the global EEAllocContext. Absent in runtimes which only allocate out 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 |
Expand Down Expand Up @@ -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 */);
}
Expand Down Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion docs/design/datacontracts/data-descriptor-meanings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "Optional. Pointer to the global EEAllocContext. Absent in runtimes which only allocate out 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",
Expand Down
9 changes: 1 addition & 8 deletions src/coreclr/debug/daccess/dacdbiimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down
7 changes: 4 additions & 3 deletions src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 0 additions & 7 deletions src/coreclr/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/inc/dacvars.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
174 changes: 0 additions & 174 deletions src/coreclr/vm/amd64/AllocSlow.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
;
Expand Down Expand Up @@ -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
1 change: 0 additions & 1 deletion src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 0 additions & 6 deletions src/coreclr/vm/gccover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
35 changes: 12 additions & 23 deletions src/coreclr/vm/gcenv.ee.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}


Expand Down
Loading