Skip to content

Commit db2d27c

Browse files
committed
Move most of the logic for the trial box allocation into gchelpers.cpp
1 parent 6d47a48 commit db2d27c

File tree

3 files changed

+151
-127
lines changed

3 files changed

+151
-127
lines changed

src/coreclr/vm/gchelpers.cpp

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,100 @@ inline gc_alloc_context* GetThreadAllocContext()
4949
return & GetThread()->m_alloc_context;
5050
}
5151

52+
// When not using per-thread allocation contexts, we (the EE) need to take care that
53+
// no two threads are concurrently modifying the global allocation context. This lock
54+
// must be acquired before any sort of operations involving the global allocation context
55+
// can occur.
56+
//
57+
// This lock is acquired by all allocations when not using per-thread allocation contexts.
58+
// It is acquired in two kinds of places:
59+
// 1) JIT_TrialAllocFastSP (and related assembly alloc helpers), which attempt to
60+
// acquire it but move into an alloc slow path if acquiring fails
61+
// (but does not decrement the lock variable when doing so)
62+
// 2) Alloc in gchelpers.cpp, which acquire the lock using
63+
// the Acquire and Release methods below.
64+
class GlobalAllocLock {
65+
friend struct AsmOffsets;
66+
private:
67+
// The lock variable. This field must always be first.
68+
LONG m_lock;
69+
70+
public:
71+
// Creates a new GlobalAllocLock in the unlocked state.
72+
GlobalAllocLock() : m_lock(-1) {}
73+
74+
// Copy and copy-assignment operators should never be invoked
75+
// for this type
76+
GlobalAllocLock(const GlobalAllocLock&) = delete;
77+
GlobalAllocLock& operator=(const GlobalAllocLock&) = delete;
78+
79+
bool TryAcquire()
80+
{
81+
CONTRACTL {
82+
NOTHROW;
83+
GC_TRIGGERS; // switch to preemptive mode
84+
MODE_COOPERATIVE;
85+
} CONTRACTL_END;
86+
87+
return InterlockedCompareExchange(&m_lock, 0, -1) == -1;
88+
}
89+
90+
// Acquires the lock, spinning if necessary to do so. When this method
91+
// returns, m_lock will be zero and the lock will be acquired.
92+
void Acquire()
93+
{
94+
CONTRACTL {
95+
NOTHROW;
96+
GC_TRIGGERS; // switch to preemptive mode
97+
MODE_COOPERATIVE;
98+
} CONTRACTL_END;
99+
100+
DWORD spinCount = 0;
101+
while(InterlockedExchange(&m_lock, 0) != -1)
102+
{
103+
GCX_PREEMP();
104+
__SwitchToThread(0, spinCount++);
105+
}
106+
107+
assert(m_lock == 0);
108+
}
109+
110+
// Releases the lock.
111+
void Release()
112+
{
113+
LIMITED_METHOD_CONTRACT;
114+
115+
// the lock may not be exactly 0. This is because the
116+
// assembly alloc routines increment the lock variable and
117+
// jump if not zero to the slow alloc path, which eventually
118+
// will try to acquire the lock again. At that point, it will
119+
// spin in Acquire (since m_lock is some number that's not zero).
120+
// When the thread that /does/ hold the lock releases it, the spinning
121+
// thread will continue.
122+
MemoryBarrier();
123+
assert(m_lock >= 0);
124+
m_lock = -1;
125+
}
126+
127+
// Static helper to acquire a lock, for use with the Holder template.
128+
static void AcquireLock(GlobalAllocLock *lock)
129+
{
130+
WRAPPER_NO_CONTRACT;
131+
lock->Acquire();
132+
}
133+
134+
// Static helper to release a lock, for use with the Holder template
135+
static void ReleaseLock(GlobalAllocLock *lock)
136+
{
137+
WRAPPER_NO_CONTRACT;
138+
lock->Release();
139+
}
140+
141+
typedef Holder<GlobalAllocLock *, GlobalAllocLock::AcquireLock, GlobalAllocLock::ReleaseLock> Holder;
142+
};
143+
144+
typedef GlobalAllocLock::Holder GlobalAllocLockHolder;
145+
52146
struct AsmOffsets {
53147
static_assert(offsetof(GlobalAllocLock, m_lock) == 0, "ASM code relies on this property");
54148
};
@@ -1437,3 +1531,51 @@ SetCardsAfterBulkCopy(Object **start, size_t len)
14371531
#if defined(_MSC_VER) && defined(TARGET_X86)
14381532
#pragma optimize("", on) // Go back to command line default optimizations
14391533
#endif //_MSC_VER && TARGET_X86
1534+
1535+
OBJECTREF TryBoxFromTrialAllocation(MethodTable* pMT, void* unboxedData)
1536+
{
1537+
// Use the relevant allocation context to try to do a trail allocation
1538+
// without calling into the GC.
1539+
gc_alloc_context* pAllocContext;
1540+
if (GCHeapUtilities::UseThreadAllocationContexts())
1541+
{
1542+
pAllocContext = GetThreadAllocContext();
1543+
}
1544+
else
1545+
{
1546+
pAllocContext = &g_global_alloc_context;
1547+
}
1548+
1549+
uint32_t size = pMT->GetNumInstanceFieldBytes();
1550+
1551+
if (!GCHeapUtilities::UseThreadAllocationContexts())
1552+
{
1553+
// Don't wait on the global alloc lock if we can't acquire it.
1554+
if (!g_global_alloc_lock.TryAcquire())
1555+
{
1556+
return ObjectToOBJECTREF(nullptr);
1557+
}
1558+
}
1559+
1560+
if (pAllocContext->alloc_limit - pAllocContext->alloc_ptr < size)
1561+
{
1562+
if (!GCHeapUtilities::UseThreadAllocationContexts())
1563+
{
1564+
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
1565+
}
1566+
// Tail call to the slow helper, we can't allocate from the alloc context.
1567+
return nullptr;
1568+
}
1569+
1570+
OBJECTREF objRef = ObjectToOBJECTREF((Object*)pAllocContext->alloc_ptr);
1571+
pAllocContext->alloc_ptr += size;
1572+
1573+
if (!GCHeapUtilities::UseThreadAllocationContexts())
1574+
{
1575+
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
1576+
}
1577+
1578+
CopyValueClass(objRef->UnBox(), unboxedData, pMT);
1579+
1580+
return objRef;
1581+
}

src/coreclr/vm/gchelpers.h

Lines changed: 4 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ inline OBJECTREF AllocateObject(MethodTable *pMT
7272
);
7373
}
7474

75+
// Try making a box of the unboxed data using the trial allocation context.
76+
// If unsuccessful, returns nullptr.
77+
OBJECTREF TryBoxFromTrialAllocation(MethodTable* pMT, void* unboxedData);
78+
7579
extern int StompWriteBarrierEphemeral(bool isRuntimeSuspended);
7680
extern int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck);
7781
extern int SwitchToWriteWatchBarrier(bool isRuntimeSuspended);
@@ -91,97 +95,4 @@ void SetCardsAfterBulkCopy(Object **start, size_t len);
9195

9296
void PublishFrozenObject(Object*& orObject);
9397

94-
// When not using per-thread allocation contexts, we (the EE) need to take care that
95-
// no two threads are concurrently modifying the global allocation context. This lock
96-
// must be acquired before any sort of operations involving the global allocation context
97-
// can occur.
98-
//
99-
// This lock is acquired by all allocations when not using per-thread allocation contexts.
100-
// It is acquired in two kinds of places:
101-
// 1) JIT_TrialAllocFastSP (and related assembly alloc helpers), which attempt to
102-
// acquire it but move into an alloc slow path if acquiring fails
103-
// (but does not decrement the lock variable when doing so)
104-
// 2) Alloc in gchelpers.cpp, which acquire the lock using
105-
// the Acquire and Release methods below.
106-
class GlobalAllocLock {
107-
friend struct AsmOffsets;
108-
private:
109-
// The lock variable. This field must always be first.
110-
LONG m_lock;
111-
112-
public:
113-
// Creates a new GlobalAllocLock in the unlocked state.
114-
GlobalAllocLock() : m_lock(-1) {}
115-
116-
// Copy and copy-assignment operators should never be invoked
117-
// for this type
118-
GlobalAllocLock(const GlobalAllocLock&) = delete;
119-
GlobalAllocLock& operator=(const GlobalAllocLock&) = delete;
120-
121-
// Acquires the lock, spinning if necessary to do so. When this method
122-
// returns, m_lock will be zero and the lock will be acquired.
123-
void Acquire()
124-
{
125-
CONTRACTL {
126-
NOTHROW;
127-
GC_TRIGGERS; // switch to preemptive mode
128-
MODE_COOPERATIVE;
129-
} CONTRACTL_END;
130-
131-
DWORD spinCount = 0;
132-
while(InterlockedExchange(&m_lock, 0) != -1)
133-
{
134-
GCX_PREEMP();
135-
__SwitchToThread(0, spinCount++);
136-
}
137-
138-
assert(m_lock == 0);
139-
}
140-
141-
// Releases the lock.
142-
void Release()
143-
{
144-
LIMITED_METHOD_CONTRACT;
145-
146-
// the lock may not be exactly 0. This is because the
147-
// assembly alloc routines increment the lock variable and
148-
// jump if not zero to the slow alloc path, which eventually
149-
// will try to acquire the lock again. At that point, it will
150-
// spin in Acquire (since m_lock is some number that's not zero).
151-
// When the thread that /does/ hold the lock releases it, the spinning
152-
// thread will continue.
153-
MemoryBarrier();
154-
assert(m_lock >= 0);
155-
m_lock = -1;
156-
}
157-
158-
// Static helper to acquire a lock, for use with the Holder template.
159-
static void AcquireLock(GlobalAllocLock *lock)
160-
{
161-
WRAPPER_NO_CONTRACT;
162-
lock->Acquire();
163-
}
164-
165-
// Static helper to release a lock, for use with the Holder template
166-
static void ReleaseLock(GlobalAllocLock *lock)
167-
{
168-
WRAPPER_NO_CONTRACT;
169-
lock->Release();
170-
}
171-
172-
typedef Holder<GlobalAllocLock *, GlobalAllocLock::AcquireLock, GlobalAllocLock::ReleaseLock> Holder;
173-
};
174-
175-
typedef GlobalAllocLock::Holder GlobalAllocLockHolder;
176-
177-
// For single-proc machines, the global allocation context is protected
178-
// from concurrent modification by this lock.
179-
//
180-
// When not using per-thread allocation contexts, certain methods on IGCHeap
181-
// require that this lock be held before calling. These methods are documented
182-
// on the IGCHeap interface.
183-
extern "C"
184-
{
185-
extern GlobalAllocLock g_global_alloc_lock;
186-
}
18798
#endif // _GCHELPERS_H_

src/coreclr/vm/jithelpers.cpp

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "float.h" // for isnan
1919
#include "dbginterface.h"
2020
#include "dllimport.h"
21+
#include "gchelpers.h"
2122
#include "gcheaputilities.h"
2223
#include "comdelegate.h"
2324
#include "corprof.h"
@@ -2858,45 +2859,15 @@ HCIMPL2(Object*, JIT_Box, CORINFO_CLASS_HANDLE type, void* unboxedData)
28582859
FCThrow(kNullReferenceException);
28592860
}
28602861

2861-
// Use the relevant allocation context to try to do a trail allocation
2862-
// without calling into the GC.
2863-
gc_alloc_context* pAllocContext;
2864-
if (GCHeapUtilities::UseThreadAllocationContexts())
2865-
{
2866-
pAllocContext = GetThread()->GetAllocContext();
2867-
}
2868-
else
2869-
{
2870-
pAllocContext = &g_global_alloc_context;
2871-
}
2872-
2873-
uint32_t size = clsHnd.GetSize();
2874-
2875-
if (!GCHeapUtilities::UseThreadAllocationContexts())
2876-
{
2877-
GlobalAllocLock::AcquireLock(&g_global_alloc_lock);
2878-
}
2862+
OBJECTREF newobj = TryBoxFromTrialAllocation(pMT, unboxedData);
28792863

2880-
if (pAllocContext->alloc_limit - pAllocContext->alloc_ptr < size)
2864+
if (newobj == nullptr)
28812865
{
2882-
if (!GCHeapUtilities::UseThreadAllocationContexts())
2883-
{
2884-
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
2885-
}
2886-
// Tail call to the slow helper, we can't allocate from the alloc context.
2866+
// We failed to allocate with a trial allocation.
2867+
// Fall back to the framed helper.
28872868
return HCCALL2(JIT_Box_Framed, type, unboxedData);
28882869
}
28892870

2890-
OBJECTREF newobj = ObjectToOBJECTREF((Object*)pAllocContext->alloc_ptr);
2891-
pAllocContext->alloc_ptr += size;
2892-
2893-
if (!GCHeapUtilities::UseThreadAllocationContexts())
2894-
{
2895-
GlobalAllocLock::ReleaseLock(&g_global_alloc_lock);
2896-
}
2897-
2898-
CopyValueClass(newobj->UnBox(), unboxedData, clsHnd.AsMethodTable());
2899-
29002871
return(OBJECTREFToObject(newobj));
29012872
}
29022873
HCIMPLEND

0 commit comments

Comments
 (0)