Skip to content

Commit

Permalink
Move most of the logic for the trial box allocation into gchelpers.cpp
Browse files Browse the repository at this point in the history
  • Loading branch information
jkoritzinsky committed Apr 26, 2024
1 parent 6d47a48 commit db2d27c
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 127 deletions.
142 changes: 142 additions & 0 deletions src/coreclr/vm/gchelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,100 @@ inline gc_alloc_context* GetThreadAllocContext()
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 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;

bool TryAcquire()
{
CONTRACTL {
NOTHROW;
GC_TRIGGERS; // switch to preemptive mode
MODE_COOPERATIVE;
} CONTRACTL_END;

return InterlockedCompareExchange(&m_lock, 0, -1) == -1;
}

// 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 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");
};
Expand Down Expand Up @@ -1437,3 +1531,51 @@ SetCardsAfterBulkCopy(Object **start, size_t len)
#if defined(_MSC_VER) && defined(TARGET_X86)
#pragma optimize("", on) // Go back to command line default optimizations
#endif //_MSC_VER && TARGET_X86

OBJECTREF TryBoxFromTrialAllocation(MethodTable* pMT, void* unboxedData)
{
// Use the relevant allocation context to try to do a trail allocation
// without calling into the GC.
gc_alloc_context* pAllocContext;
if (GCHeapUtilities::UseThreadAllocationContexts())
{
pAllocContext = GetThreadAllocContext();
}
else
{
pAllocContext = &g_global_alloc_context;
}

uint32_t size = pMT->GetNumInstanceFieldBytes();

if (!GCHeapUtilities::UseThreadAllocationContexts())
{
// Don't wait on the global alloc lock if we can't acquire it.
if (!g_global_alloc_lock.TryAcquire())
{
return ObjectToOBJECTREF(nullptr);
}
}

if (pAllocContext->alloc_limit - pAllocContext->alloc_ptr < size)
{
if (!GCHeapUtilities::UseThreadAllocationContexts())
{
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
}
// Tail call to the slow helper, we can't allocate from the alloc context.
return nullptr;
}

OBJECTREF objRef = ObjectToOBJECTREF((Object*)pAllocContext->alloc_ptr);
pAllocContext->alloc_ptr += size;

if (!GCHeapUtilities::UseThreadAllocationContexts())
{
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
}

CopyValueClass(objRef->UnBox(), unboxedData, pMT);

return objRef;
}
97 changes: 4 additions & 93 deletions src/coreclr/vm/gchelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ inline OBJECTREF AllocateObject(MethodTable *pMT
);
}

// Try making a box of the unboxed data using the trial allocation context.
// If unsuccessful, returns nullptr.
OBJECTREF TryBoxFromTrialAllocation(MethodTable* pMT, void* unboxedData);

extern int StompWriteBarrierEphemeral(bool isRuntimeSuspended);
extern int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck);
extern int SwitchToWriteWatchBarrier(bool isRuntimeSuspended);
Expand All @@ -91,97 +95,4 @@ void SetCardsAfterBulkCopy(Object **start, size_t len);

void PublishFrozenObject(Object*& orObject);

// 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 Holder<GlobalAllocLock *, GlobalAllocLock::AcquireLock, GlobalAllocLock::ReleaseLock> Holder;
};

typedef GlobalAllocLock::Holder GlobalAllocLockHolder;

// 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"
{
extern GlobalAllocLock g_global_alloc_lock;
}
#endif // _GCHELPERS_H_
39 changes: 5 additions & 34 deletions src/coreclr/vm/jithelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "float.h" // for isnan
#include "dbginterface.h"
#include "dllimport.h"
#include "gchelpers.h"
#include "gcheaputilities.h"
#include "comdelegate.h"
#include "corprof.h"
Expand Down Expand Up @@ -2858,45 +2859,15 @@ HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData)
FCThrow(kNullReferenceException);
}

// Use the relevant allocation context to try to do a trail allocation
// without calling into the GC.
gc_alloc_context* pAllocContext;
if (GCHeapUtilities::UseThreadAllocationContexts())
{
pAllocContext = GetThread()->GetAllocContext();
}
else
{
pAllocContext = &g_global_alloc_context;
}

uint32_t size = clsHnd.GetSize();

if (!GCHeapUtilities::UseThreadAllocationContexts())
{
GlobalAllocLock::AcquireLock(&g_global_alloc_lock);
}
OBJECTREF newobj = TryBoxFromTrialAllocation(pMT, unboxedData);

if (pAllocContext->alloc_limit - pAllocContext->alloc_ptr < size)
if (newobj == nullptr)
{
if (!GCHeapUtilities::UseThreadAllocationContexts())
{
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
}
// Tail call to the slow helper, we can't allocate from the alloc context.
// We failed to allocate with a trial allocation.
// Fall back to the framed helper.
return HCCALL2(JIT_Box_Framed, type, unboxedData);
}

OBJECTREF newobj = ObjectToOBJECTREF((Object*)pAllocContext->alloc_ptr);
pAllocContext->alloc_ptr += size;

if (!GCHeapUtilities::UseThreadAllocationContexts())
{
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
}

CopyValueClass(newobj->UnBox(), unboxedData, clsHnd.AsMethodTable());

return(OBJECTREFToObject(newobj));
}
HCIMPLEND
Expand Down

0 comments on commit db2d27c

Please sign in to comment.