// See comments in reset_memory.
BOOL reset_mm_p = TRUE;
+bool g_fFinalizerRunOnShutDown = false;
+
+#ifdef FEATURE_SVR_GC
+bool g_built_with_svr_gc = true;
+#else
+bool g_built_with_svr_gc = false;
+#endif // FEATURE_SVR_GC
+
+#if defined(BUILDENV_DEBUG)
+uint8_t g_build_variant = 0;
+#elif defined(BUILDENV_CHECKED)
+uint8_t g_build_variant = 1;
+#else
+uint8_t g_build_variant = 2;
+#endif // defined(BUILDENV_DEBUG)
+
+VOLATILE(int32_t) g_no_gc_lock = -1;
+
#if defined (TRACE_GC) && !defined (DACCESS_COMPILE)
const char * const allocation_state_str[] = {
"start",
};
#endif //TRACE_GC && !DACCESS_COMPILE
-
// Keep this in sync with the definition of gc_reason
#if (defined(DT_LOG) || defined(TRACE_GC)) && !defined (DACCESS_COMPILE)
static const char* const str_gc_reasons[] =
}
#endif
+
#ifdef GC_STATS
// There is a current and a prior copy of the statistics. This allows us to display deltas per reporting
// interval, as well as running totals. The 'min' and 'max' values require special treatment. They are
#ifndef MULTIPLE_HEAPS
-extern "C" {
- generation generation_table[NUMBERGENERATIONS + 1];
-}
-
alloc_list gc_heap::loh_alloc_list [NUM_LOH_ALIST-1];
alloc_list gc_heap::gen2_alloc_list[NUM_GEN2_ALIST-1];
CFinalize* gc_heap::finalize_queue = 0;
#endif // FEATURE_PREMORTEM_FINALIZATION
-#ifdef MULTIPLE_HEAPS
generation gc_heap::generation_table [NUMBERGENERATIONS + 1];
-#endif // MULTIPLE_HEAPS
size_t gc_heap::interesting_data_per_heap[max_idp_count];
//as opposed to concurrent heap verification
void gc_heap::fix_youngest_allocation_area (BOOL for_gc_p)
{
- assert (alloc_allocated);
- alloc_context* acontext = generation_alloc_context (youngest_generation);
- dprintf (3, ("generation 0 alloc context: ptr: %Ix, limit %Ix",
- (size_t)acontext->alloc_ptr, (size_t)acontext->alloc_limit));
- fix_allocation_context (acontext, for_gc_p, get_alignment_constant (TRUE));
- if (for_gc_p)
- {
- acontext->alloc_ptr = alloc_allocated;
- acontext->alloc_limit = acontext->alloc_ptr;
- }
- heap_segment_allocated (ephemeral_heap_segment) =
- alloc_allocated;
+ UNREFERENCED_PARAMETER(for_gc_p);
+
+ // The gen 0 alloc context is never used for allocation in the allocator path. It's
+ // still used in the allocation path during GCs.
+ assert (generation_allocation_pointer (youngest_generation) == nullptr);
+ assert (generation_allocation_limit (youngest_generation) == nullptr);
+ heap_segment_allocated (ephemeral_heap_segment) = alloc_allocated;
}
void gc_heap::fix_large_allocation_area (BOOL for_gc_p)
void gc_heap::repair_allocation_contexts (BOOL repair_p)
{
GCToEEInterface::GcEnumAllocContexts (repair_p ? repair_allocation : void_allocation, NULL);
-
- alloc_context* acontext = generation_alloc_context (youngest_generation);
- if (repair_p)
- repair_allocation (acontext, NULL);
- else
- void_allocation (acontext, NULL);
}
struct fix_alloc_context_args
fix_alloc_context_args args;
args.for_gc_p = for_gc_p;
args.heap = __this;
- GCToEEInterface::GcEnumAllocContexts(fix_alloc_context, &args);
+ GCToEEInterface::GcEnumAllocContexts(fix_alloc_context, &args);
fix_youngest_allocation_area(for_gc_p);
fix_large_allocation_area(for_gc_p);
}
to_gen_number = from_gen_number + (settings.promotion ? 1 : 0);
}
- dprintf (3, ("aic gen%d: s: %Id", gen->gen_num, size));
+ dprintf (3, ("aic gen%d: s: %Id, %d->%d, %Ix->%Ix", gen->gen_num, size, from_gen_number,
+ to_gen_number, generation_allocation_pointer(gen), generation_allocation_limit(gen)));
int pad_in_front = (old_loc != 0) ? USE_PADDING_FRONT : 0;
#endif // FEATURE_REDHAWK
#endif //FEATURE_PREMORTEM_FINALIZATION
-inline
-static void spin_lock ()
-{
- enter_spin_lock_noinstru (&m_GCLock);
-}
-
-inline
-void EnterAllocLock()
-{
- spin_lock();
-}
-
-inline
-void LeaveAllocLock()
-{
- // Trick this out
- leave_spin_lock_noinstru (&m_GCLock);
-}
-class AllocLockHolder
+class NoGCRegionLockHolder
{
public:
- AllocLockHolder()
+ NoGCRegionLockHolder()
{
- EnterAllocLock();
+ enter_spin_lock_noinstru(&g_no_gc_lock);
}
- ~AllocLockHolder()
+ ~NoGCRegionLockHolder()
{
- LeaveAllocLock();
+ leave_spin_lock_noinstru(&g_no_gc_lock);
}
};
{
HRESULT hres = S_OK;
- //Initialize all of the instance members.
-
-#ifdef MULTIPLE_HEAPS
- m_GCLock = -1;
-#endif //MULTIPLE_HEAPS
-
- // Rest of the initialization
-
#ifdef MULTIPLE_HEAPS
if ((pGenGCHeap = gc_heap::make_gc_heap(this, (int)hn)) == 0)
hres = E_OUTOFMEMORY;
// Small Object Allocator
//
//
-Object *
-GCHeap::Alloc( size_t size, uint32_t flags REQD_ALIGN_DCL)
-{
- CONTRACTL {
- NOTHROW;
- GC_TRIGGERS;
- } CONTRACTL_END;
-
- TRIGGERSGC();
-
- Object* newAlloc = NULL;
-
-#ifdef TRACE_GC
-#ifdef COUNT_CYCLES
- AllocStart = GetCycleCount32();
- unsigned finish;
-#elif defined(ENABLE_INSTRUMENTATION)
- unsigned AllocStart = GetInstLogTime();
- unsigned finish;
-#endif //COUNT_CYCLES
-#endif //TRACE_GC
-
-#ifdef MULTIPLE_HEAPS
- //take the first heap....
- gc_heap* hp = gc_heap::g_heaps[0];
-#else
- gc_heap* hp = pGenGCHeap;
-#ifdef _PREFAST_
- // prefix complains about us dereferencing hp in wks build even though we only access static members
- // this way. not sure how to shut it up except for this ugly workaround:
- PREFIX_ASSUME(hp != NULL);
-#endif //_PREFAST_
-#endif //MULTIPLE_HEAPS
-
- {
- AllocLockHolder lh;
-
-#ifndef FEATURE_REDHAWK
- GCStress<gc_on_alloc>::MaybeTrigger(generation_alloc_context(hp->generation_of(0)));
-#endif // FEATURE_REDHAWK
-
- alloc_context* acontext = 0;
-
- if (size < LARGE_OBJECT_SIZE)
- {
- acontext = generation_alloc_context (hp->generation_of (0));
-
-#ifdef TRACE_GC
- AllocSmallCount++;
-#endif //TRACE_GC
- newAlloc = (Object*) hp->allocate (size + ComputeMaxStructAlignPad(requiredAlignment), acontext);
-#ifdef FEATURE_STRUCTALIGN
- newAlloc = (Object*) hp->pad_for_alignment ((uint8_t*) newAlloc, requiredAlignment, size, acontext);
-#endif // FEATURE_STRUCTALIGN
- // ASSERT (newAlloc);
- }
- else
- {
- acontext = generation_alloc_context (hp->generation_of (max_generation+1));
-
- newAlloc = (Object*) hp->allocate_large_object (size + ComputeMaxStructAlignPadLarge(requiredAlignment), acontext->alloc_bytes_loh);
-#ifdef FEATURE_STRUCTALIGN
- newAlloc = (Object*) hp->pad_for_alignment_large ((uint8_t*) newAlloc, requiredAlignment, size);
-#endif // FEATURE_STRUCTALIGN
- }
- }
-
- CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(newAlloc, size, flags & GC_ALLOC_FINALIZE);
-
-#ifdef TRACE_GC
-#ifdef COUNT_CYCLES
- finish = GetCycleCount32();
-#elif defined(ENABLE_INSTRUMENTATION)
- finish = GetInstLogTime();
-#endif //COUNT_CYCLES
- AllocDuration += finish - AllocStart;
- AllocCount++;
-#endif //TRACE_GC
- return newAlloc;
-}
-
-// Allocate small object with an alignment requirement of 8-bytes. Non allocation context version.
-Object *
-GCHeap::AllocAlign8( size_t size, uint32_t flags)
-{
-#ifdef FEATURE_64BIT_ALIGNMENT
- CONTRACTL {
- NOTHROW;
- GC_TRIGGERS;
- } CONTRACTL_END;
-
- Object* newAlloc = NULL;
-
- {
- AllocLockHolder lh;
-
-#ifdef MULTIPLE_HEAPS
- //take the first heap....
- gc_heap* hp = gc_heap::g_heaps[0];
-#else
- gc_heap* hp = pGenGCHeap;
-#endif //MULTIPLE_HEAPS
-
- newAlloc = AllocAlign8Common(hp, generation_alloc_context (hp->generation_of (0)), size, flags);
- }
-
- return newAlloc;
-#else
- UNREFERENCED_PARAMETER(size);
- UNREFERENCED_PARAMETER(flags);
- assert(!"should not call GCHeap::AllocAlign8 without FEATURE_64BIT_ALIGNMENT defined!");
- return nullptr;
-#endif //FEATURE_64BIT_ALIGNMENT
-}
-
-// Allocate small object with an alignment requirement of 8-bytes. Allocation context version.
+// Allocate small object with an alignment requirement of 8-bytes.
Object*
GCHeap::AllocAlign8(gc_alloc_context* ctx, size_t size, uint32_t flags )
{
int GCHeap::StartNoGCRegion(uint64_t totalSize, BOOL lohSizeKnown, uint64_t lohSize, BOOL disallowFullBlockingGC)
{
- AllocLockHolder lh;
+ NoGCRegionLockHolder lh;
dprintf (1, ("begin no gc called"));
start_no_gc_region_status status = gc_heap::prepare_for_no_gc_region (totalSize, lohSizeKnown, lohSize, disallowFullBlockingGC);
int GCHeap::EndNoGCRegion()
{
- AllocLockHolder lh;
+ NoGCRegionLockHolder lh;
return (int)gc_heap::end_no_gc_region();
}
#endif //BACKGROUND_GC
}
+void GCHeap::SetFinalizeRunOnShutdown(bool value)
+{
+ g_fFinalizerRunOnShutDown = value;
+}
+
void PopulateDacVars(GcDacVars *gcDacVars)
{
#ifndef DACCESS_COMPILE
gcDacVars->next_sweep_obj = &gc_heap::next_sweep_obj;
gcDacVars->oom_info = &gc_heap::oom_info;
gcDacVars->finalize_queue = reinterpret_cast<dac_finalize_queue**>(&gc_heap::finalize_queue);
- gcDacVars->generation_table = reinterpret_cast<dac_generation**>(&generation_table);
+ gcDacVars->generation_table = reinterpret_cast<dac_generation**>(&gc_heap::generation_table);
#ifdef GC_CONFIG_DRIVEN
gcDacVars->gc_global_mechanisms = reinterpret_cast<size_t**>(&gc_global_mechanisms);
gcDacVars->interesting_data_per_heap = reinterpret_cast<size_t**>(&gc_heap::interesting_data_per_heap);
extern "C" uint32_t* g_gc_card_table;
extern "C" uint8_t* g_gc_lowest_address;
extern "C" uint8_t* g_gc_highest_address;
-extern "C" bool g_fFinalizerRunOnShutDown;
-extern "C" bool g_built_with_svr_gc;
-extern "C" uint8_t g_build_variant;
namespace WKS {
::IGCHeapInternal* CreateGCHeap();
return mt->GetBaseSize() >= LARGE_OBJECT_SIZE;
}
- void SetFinalizeRunOnShutdown(bool value)
- {
- g_fFinalizerRunOnShutDown = value;
- }
-
protected:
public:
#if defined(FEATURE_BASICFREEZE) && defined(VERIFY_HEAP)
uint8_t* g_gc_lowest_address = 0;
uint8_t* g_gc_highest_address = 0;
-bool g_fFinalizerRunOnShutDown = false;
-
-#ifdef FEATURE_SVR_GC
-bool g_built_with_svr_gc = true;
-#else
-bool g_built_with_svr_gc = false;
-#endif // FEATURE_SVR_GC
-
-#if defined(BUILDENV_DEBUG)
-uint8_t g_build_variant = 0;
-#elif defined(BUILDENV_CHECKED)
-uint8_t g_build_variant = 1;
-#else
-uint8_t g_build_variant = 2;
-#endif // defined(BUILDENV_DEBUG)
-
-VOLATILE(int32_t) m_GCLock = -1;
#ifdef GC_CONFIG_DRIVEN
void record_global_mechanism (int mech_index)
class gc_heap;
class CFinalize;
+extern bool g_fFinalizerRunOnShutDown;
+extern bool g_built_with_svr_gc;
+extern uint8_t g_build_variant;
+extern VOLATILE(int32_t) g_no_gc_lock;
+
class GCHeap : public IGCHeapInternal
{
protected:
HRESULT Initialize ();
//flags can be GC_ALLOC_CONTAINS_REF GC_ALLOC_FINALIZE
- Object* Alloc (size_t size, uint32_t flags);
- Object* AllocAlign8 (size_t size, uint32_t flags);
Object* AllocAlign8 (gc_alloc_context* acontext, size_t size, uint32_t flags);
private:
Object* AllocAlign8Common (void* hp, alloc_context* acontext, size_t size, uint32_t flags);
BOOL ShouldRestartFinalizerWatchDog();
void DiagWalkObject (Object* obj, walk_fn fn, void* context);
+ void SetFinalizeRunOnShutdown(bool value);
public: // FIX
// For low memory notification from host
extern int32_t g_bLowMemoryFromHost;
-extern VOLATILE(int32_t) m_GCLock;
-
// !!!!!!!!!!!!!!!!!!!!!!!
// make sure you change the def in bcl\system\gc.cs
// if you change this!
*/
// Allocates an object on the given allocation context with the given size and flags.
+ // It is the responsibility of the caller to ensure that the passed-in alloc context is
+ // owned by the thread that is calling this function. If using per-thread alloc contexts,
+ // no lock is needed; callers not using per-thread alloc contexts will need to acquire
+ // a lock to ensure that the calling thread has unique ownership over this alloc context;
virtual Object* Alloc(gc_alloc_context* acontext, size_t size, uint32_t flags) = 0;
- // Allocates an object on the default allocation context with the given size and flags.
- virtual Object* Alloc(size_t size, uint32_t flags) = 0;
-
// Allocates an object on the large object heap with the given size and flags.
virtual Object* AllocLHeap(size_t size, uint32_t flags) = 0;
- // Allocates an object on the default allocation context, aligned to 64 bits,
- // with the given size and flags.
- virtual Object* AllocAlign8 (size_t size, uint32_t flags) = 0;
-
// Allocates an object on the given allocation context, aligned to 64 bits,
// with the given size and flags.
- virtual Object* AllocAlign8 (gc_alloc_context* acontext, size_t size, uint32_t flags) = 0;
+ // It is the responsibility of the caller to ensure that the passed-in alloc context is
+ // owned by the thread that is calling this function. If using per-thread alloc contexts,
+ // no lock is needed; callers not using per-thread alloc contexts will need to acquire
+ // a lock to ensure that the calling thread has unique ownership over this alloc context.
+ virtual Object* AllocAlign8(gc_alloc_context* acontext, size_t size, uint32_t flags) = 0;
// This is for the allocator to indicate it's done allocating a large object during a
// background GC as the BGC threads also need to walk LOH.
PER_HEAP
BOOL heap_analyze_success;
-#ifdef MULTIPLE_HEAPS
// The generation table. Must always be last.
PER_HEAP
generation generation_table [NUMBERGENERATIONS + 1];
-#endif // MULTIPLE_HEAPS
// End DAC zone
}
#endif //MULTIPLE_HEAPS
-#ifndef MULTIPLE_HEAPS
-extern "C" {
- extern generation generation_table[NUMBERGENERATIONS + 1];
-}
-#endif // MULTIPLE_HEAPS
-
inline
generation* gc_heap::generation_of (int n)
{
EEConfig * g_pConfig;
+gc_alloc_context g_global_alloc_context;
+
bool CLREventStatic::CreateManualEventNoThrow(bool bInitialState)
{
m_hEvent = CreateEventW(NULL, TRUE, bInitialState, NULL);
-; <TODO> this m_GCLock should be a size_t so we don't have a store-forwarding penalty in the code below.
-; Unfortunately, the compiler intrinsic for InterlockedExchangePointer seems to be broken and we
-; get bad code gen in gc.cpp on IA64. </TODO>
-M_GCLOCK equ ?m_GCLock@@3HC
-extern M_GCLOCK:dword
-extern generation_table:qword
+extern g_global_alloc_lock:dword
+extern g_global_alloc_context:qword
LEAF_ENTRY JIT_TrialAllocSFastSP, _TEXT
; m_BaseSize is guaranteed to be a multiple of 8.
- inc [M_GCLOCK]
+ inc [g_global_alloc_lock]
jnz JIT_NEW
- mov rax, [generation_table + 0] ; alloc_ptr
- mov r10, [generation_table + 8] ; limit_ptr
+ mov rax, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr] ; alloc_ptr
+ mov r10, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_limit] ; limit_ptr
add r8, rax
cmp r8, r10
ja AllocFailed
- mov qword ptr [generation_table + 0], r8 ; update the alloc ptr
+ mov qword ptr [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr], r8 ; update the alloc ptr
mov [rax], rcx
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
ifdef _DEBUG
call DEBUG_TrialAllocSetAppDomain_NoScratchArea
ret
AllocFailed:
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
jmp JIT_NEW
LEAF_END JIT_TrialAllocSFastSP, _TEXT
; m_BaseSize is guaranteed to be a multiple of 8.
- inc [M_GCLOCK]
+ inc [g_global_alloc_lock]
jnz JIT_Box
- mov rax, [generation_table + 0] ; alloc_ptr
- mov r10, [generation_table + 8] ; limit_ptr
+ mov rax, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr] ; alloc_ptr
+ mov r10, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_limit] ; limit_ptr
add r8, rax
ja NoAlloc
- mov qword ptr [generation_table + 0], r8 ; update the alloc ptr
+ mov qword ptr [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr], r8 ; update the alloc ptr
mov [rax], rcx
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
ifdef _DEBUG
call DEBUG_TrialAllocSetAppDomain_NoScratchArea
ret
NoAlloc:
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
jmp JIT_Box
NESTED_END JIT_BoxFastUP, _TEXT
lea r8d, [r8d + ecx*2 + 7]
and r8d, -8
- inc [M_GCLOCK]
+ inc [g_global_alloc_lock]
jnz FramedAllocateString
- mov rax, [generation_table + 0] ; alloc_ptr
- mov r10, [generation_table + 8] ; limit_ptr
+ mov rax, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr] ; alloc_ptr
+ mov r10, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_limit] ; limit_ptr
add r8, rax
cmp r8, r10
ja AllocFailed
- mov qword ptr [generation_table + 0], r8 ; update the alloc ptr
+ mov qword ptr [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr], r8 ; update the alloc ptr
mov [rax], r11
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
mov [rax + OFFSETOF__StringObject__m_StringLength], ecx
ret
AllocFailed:
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
jmp FramedAllocateString
LEAF_END AllocateStringFastUP, _TEXT
add r8d, 7
and r8d, -8
- inc [M_GCLOCK]
+ inc [g_global_alloc_lock]
jnz JIT_NewArr1
- mov rax, [generation_table + 0] ; alloc_ptr
- mov r10, [generation_table + 8] ; limit_ptr
+ mov rax, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr] ; alloc_ptr
+ mov r10, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_limit] ; limit_ptr
add r8, rax
jc AllocFailed
cmp r8, r10
ja AllocFailed
- mov qword ptr [generation_table + 0], r8 ; update the alloc ptr
+ mov qword ptr [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr], r8 ; update the alloc ptr
mov [rax], r9
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx
ret
AllocFailed:
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
jmp JIT_NewArr1
LEAF_END JIT_NewArr1VC_UP, _TEXT
; No need for rounding in this case - element size is 8, and m_BaseSize is guaranteed
; to be a multiple of 8.
- inc [M_GCLOCK]
+ inc [g_global_alloc_lock]
jnz JIT_NewArr1
- mov rax, [generation_table + 0] ; alloc_ptr
- mov r10, [generation_table + 8] ; limit_ptr
+ mov rax, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr] ; alloc_ptr
+ mov r10, [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_limit] ; limit_ptr
add r8, rax
cmp r8, r10
ja AllocFailed
- mov qword ptr [generation_table + 0], r8 ; update the alloc ptr
+ mov qword ptr [g_global_alloc_context + OFFSETOF__gc_alloc_context__alloc_ptr], r8 ; update the alloc ptr
mov [rax], r9
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
mov dword ptr [rax + OFFSETOF__ArrayBase__m_NumComponents], edx
ret
AllocFailed:
- mov [M_GCLOCK], -1
+ mov [g_global_alloc_lock], -1
OversizedArray:
jmp JIT_NewArr1
#define OFFSET__Thread__m_alloc_context__alloc_limit 0x68
ASMCONSTANTS_C_ASSERT(OFFSET__Thread__m_alloc_context__alloc_limit == offsetof(Thread, m_alloc_context) + offsetof(gc_alloc_context, alloc_limit));
+#define OFFSETOF__gc_alloc_context__alloc_ptr 0x0
+ASMCONSTANT_OFFSETOF_ASSERT(gc_alloc_context, alloc_ptr);
+
+#define OFFSETOF__gc_alloc_context__alloc_limit 0x8
+ASMCONSTANT_OFFSETOF_ASSERT(gc_alloc_context, alloc_limit);
+
#define OFFSETOF__ThreadExceptionState__m_pCurrentTracker 0x000
ASMCONSTANTS_C_ASSERT(OFFSETOF__ThreadExceptionState__m_pCurrentTracker
== offsetof(ThreadExceptionState, m_pCurrentTracker));
))
{
- _ASSERTE(GCHeapUtilities::UseAllocationContexts());
+ _ASSERTE(GCHeapUtilities::UseThreadAllocationContexts());
// If the TLS for Thread is low enough use the super-fast helpers
if (gThreadTLSIndex < TLS_MINIMUM_AVAILABLE)
{
#include "clrvarargs.h" /* for VARARG C_ASSERTs in asmconstants.h */
class CheckAsmOffsets
{
-#define ASMCONSTANTS_C_ASSERT(cond) \
- typedef char UNIQUE_LABEL(__C_ASSERT__)[(cond) ? 1 : -1];
+#define ASMCONSTANTS_C_ASSERT(cond) static_assert(cond, #cond);
#include "asmconstants.h"
};
}
CONTRACTL_END;
- Thread * pThread = NULL;
- while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL)
+ if (GCHeapUtilities::UseThreadAllocationContexts())
{
- fn(pThread->GetAllocContext(), param);
+ Thread * pThread = NULL;
+ while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL)
+ {
+ fn(pThread->GetAllocContext(), param);
+ }
+ }
+ else
+ {
+ fn(&g_global_alloc_context, param);
}
}
FinalizerThread::EnableFinalization();
}
}
+
#endif // FEATURE_STANDALONE_GC
-#endif // _GCENV_EE_H_
\ No newline at end of file
+#endif // _GCENV_EE_H_
bool g_sw_ww_enabled_for_gc_heap = false;
#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
+
+gc_alloc_context g_global_alloc_context = {};
+
}
#endif // !DACCESS_COMPILE
+// 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.
+extern "C" gc_alloc_context g_global_alloc_context;
+
extern "C" uint32_t* g_card_bundle_table;
extern "C" uint8_t* g_ephemeral_low;
extern "C" uint8_t* g_ephemeral_high;
GetGCHeap()->WaitUntilGCComplete(bConsiderGCStart);
}
- // Returns true if we should be using allocation contexts, false otherwise.
- inline static bool UseAllocationContexts()
- {
- WRAPPER_NO_CONTRACT;
-#ifdef FEATURE_REDHAWK
- // SIMPLIFY: only use allocation contexts
- return true;
-#else
-#if defined(_TARGET_ARM_) || defined(FEATURE_PAL)
- return true;
-#else
- return ((IsServerHeap() ? true : (g_SystemInfo.dwNumberOfProcessors >= 2)));
-#endif
-#endif
- }
-
// Returns true if the held GC heap is a Server GC heap, false otherwise.
inline static bool IsServerHeap()
{
#endif // FEATURE_SVR_GC
}
+ static bool UseThreadAllocationContexts()
+ {
+ // When running on a single-proc 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_ARM_) || defined(FEATURE_PAL) || defined(FEATURE_REDHAWK)
+ return true;
+#else
+ return IsServerHeap() || ::GetCurrentProcessCpuCount() != 1;
+#endif
+
+ }
+
#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
// Returns True if software write watch is currently enabled for the GC Heap,
}
#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
-
private:
// This class should never be instantiated.
GCHeapUtilities() = delete;
{
WRAPPER_NO_CONTRACT;
- assert(GCHeapUtilities::UseAllocationContexts());
+ assert(GCHeapUtilities::UseThreadAllocationContexts());
return & GetThread()->m_alloc_context;
}
+// 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 and AllocAlign8 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(FastInterlockExchange(&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 Holder<GlobalAllocLock *, GlobalAllocLock::AcquireLock, GlobalAllocLock::ReleaseLock> 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
// * Call code:Alloc - When the jit helpers fall back, or we do allocations within the runtime code
// itself, we ultimately call here.
// * Call code:AllocLHeap - Used very rarely to force allocation to be on the large object heap.
-//
+//
// While this is a choke point into allocating an object, it is primitive (it does not want to know about
// MethodTable and thus does not initialize that poitner. It also does not know if the object is finalizable
// or contains pointers. Thus we quickly wrap this function in more user-friendly ones that know about
// MethodTables etc. (see code:FastAllocatePrimitiveArray code:AllocateArrayEx code:AllocateObject)
-//
+//
// You can get an exhaustive list of code sites that allocate GC objects by finding all calls to
// code:ProfilerObjectAllocatedCallback (since the profiler has to hook them all).
inline Object* Alloc(size_t size, BOOL bFinalize, BOOL bContainsPointers )
// We don't want to throw an SO during the GC, so make sure we have plenty
// of stack before calling in.
INTERIOR_STACK_PROBE_FOR(GetThread(), static_cast<unsigned>(DEFAULT_ENTRY_PROBE_AMOUNT * 1.5));
- if (GCHeapUtilities::UseAllocationContexts())
+ if (GCHeapUtilities::UseThreadAllocationContexts())
+ {
retVal = GCHeapUtilities::GetGCHeap()->Alloc(GetThreadAllocContext(), size, flags);
+ }
else
- retVal = GCHeapUtilities::GetGCHeap()->Alloc(size, flags);
+ {
+ GlobalAllocLockHolder holder(&g_global_alloc_lock);
+ retVal = GCHeapUtilities::GetGCHeap()->Alloc(&g_global_alloc_context, size, flags);
+ }
+
if (!retVal)
{
// We don't want to throw an SO during the GC, so make sure we have plenty
// of stack before calling in.
INTERIOR_STACK_PROBE_FOR(GetThread(), static_cast<unsigned>(DEFAULT_ENTRY_PROBE_AMOUNT * 1.5));
- if (GCHeapUtilities::UseAllocationContexts())
+ if (GCHeapUtilities::UseThreadAllocationContexts())
+ {
retVal = GCHeapUtilities::GetGCHeap()->AllocAlign8(GetThreadAllocContext(), size, flags);
+ }
else
- retVal = GCHeapUtilities::GetGCHeap()->AllocAlign8(size, flags);
+ {
+ GlobalAllocLockHolder holder(&g_global_alloc_lock);
+ retVal = GCHeapUtilities::GetGCHeap()->AllocAlign8(&g_global_alloc_context, size, flags);
+ }
if (!retVal)
{
#define MON_DEBUG 1
#endif
-class generation;
-extern "C" generation generation_table[];
+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
else
{
// Take the GC lock (there is no lock prefix required - we will use JIT_TrialAllocSFastMP on an MP System).
- // inc dword ptr [m_GCLock]
+ // inc dword ptr [g_global_alloc_lock]
psl->Emit16(0x05ff);
- psl->Emit32((int)(size_t)&m_GCLock);
+ psl->Emit32((int)(size_t)&g_global_alloc_lock);
// jnz NoLock
psl->X86EmitCondJump(noLock, X86CondCode::kJNZ);
psl->X86EmitIndexRegLoad(kEDX, kECX, offsetof(MethodTable, m_BaseSize));
}
- // mov eax, dword ptr [generation_table]
+ // mov eax, dword ptr [g_global_alloc_context]
psl->Emit8(0xA1);
- psl->Emit32((int)(size_t)&generation_table);
+ psl->Emit32((int)(size_t)&g_global_alloc_context);
// Try the allocation.
// add edx, eax
if (flags & (ALIGN8 | ALIGN8OBJ))
EmitAlignmentRoundup(psl, kEAX, kEDX, flags); // bump up EDX size by 12 if EAX unaligned (so that we are aligned)
- // cmp edx, dword ptr [generation_table+4]
+ // cmp edx, dword ptr [g_global_alloc_context+4]
psl->Emit16(0x153b);
- psl->Emit32((int)(size_t)&generation_table + 4);
+ psl->Emit32((int)(size_t)&g_global_alloc_context + 4);
// ja noAlloc
psl->X86EmitCondJump(noAlloc, X86CondCode::kJA);
// Fill in the allocation and get out.
- // mov dword ptr [generation_table], edx
+ // mov dword ptr [g_global_alloc_context], edx
psl->Emit16(0x1589);
- psl->Emit32((int)(size_t)&generation_table);
+ psl->Emit32((int)(size_t)&g_global_alloc_context);
if (flags & (ALIGN8 | ALIGN8OBJ))
EmitDummyObject(psl, kEAX, flags);
// mov dword ptr [eax], ecx
psl->X86EmitIndexRegStore(kEAX, 0, kECX);
- // mov dword ptr [m_GCLock], 0FFFFFFFFh
+ // mov dword ptr [g_global_alloc_lock], 0FFFFFFFFh
psl->Emit16(0x05C7);
- psl->Emit32((int)(size_t)&m_GCLock);
+ psl->Emit32((int)(size_t)&g_global_alloc_lock);
psl->Emit32(0xFFFFFFFF);
}
}
else
{
- // mov dword ptr [m_GCLock], 0FFFFFFFFh
+ // mov dword ptr [g_global_alloc_lock], 0FFFFFFFFh
psl->Emit16(0x05c7);
- psl->Emit32((int)(size_t)&m_GCLock);
+ psl->Emit32((int)(size_t)&g_global_alloc_lock);
psl->Emit32(0xFFFFFFFF);
}
}
_ASSERTE(g_SystemInfo.dwNumberOfProcessors != 0);
- JIT_TrialAlloc::Flags flags = GCHeapUtilities::UseAllocationContexts() ?
+ JIT_TrialAlloc::Flags flags = GCHeapUtilities::UseThreadAllocationContexts() ?
JIT_TrialAlloc::MP_ALLOCATOR : JIT_TrialAlloc::NORMAL;
// Get CPU features and check for SSE2 support.
do
{
- _ASSERTE(GCHeapUtilities::UseAllocationContexts());
+ _ASSERTE(GCHeapUtilities::UseThreadAllocationContexts());
// This is typically the only call in the fast path. Making the call early seems to be better, as it allows the compiler
// to use volatile registers for intermediate values. This reduces the number of push/pop instructions and eliminates
do
{
- _ASSERTE(GCHeapUtilities::UseAllocationContexts());
+ _ASSERTE(GCHeapUtilities::UseThreadAllocationContexts());
// Instead of doing elaborate overflow checks, we just limit the number of elements. This will avoid all overflow
// problems, as well as making sure big string objects are correctly allocated in the big object heap.
do
{
- _ASSERTE(GCHeapUtilities::UseAllocationContexts());
+ _ASSERTE(GCHeapUtilities::UseThreadAllocationContexts());
// Do a conservative check here. This is to avoid overflow while doing the calculations. We don't
// have to worry about "large" objects, since the allocation quantum is never big enough for
do
{
- _ASSERTE(GCHeapUtilities::UseAllocationContexts());
+ _ASSERTE(GCHeapUtilities::UseThreadAllocationContexts());
// Make sure that the total size cannot reach LARGE_OBJECT_SIZE, which also allows us to avoid overflow checks. The
// "256" slack is to cover the array header size and round-up, using a constant value here out of laziness.
))
{
// if (multi-proc || server GC)
- if (GCHeapUtilities::UseAllocationContexts())
+ if (GCHeapUtilities::UseThreadAllocationContexts())
{
#ifdef FEATURE_IMPLICIT_TLS
SetJitHelperFunction(CORINFO_HELP_NEWSFAST, JIT_NewS_MP_FastPortable);