Skip to content

Commit 76750df

Browse files
Remove helper method frames from Monitors (#113242)
- Convert to the FCALL fast path/QCALL slow path approach - Make EnterHelperResult/LeaveHelperAction into enum class so that they can safely be silently marshaled between native and managed - Move the lockTaken flag handling to managed code so we can share more helpers Benchmarking indicates this may actually be faster than the previous approach. I'm a bit skeptical, but its not out of the question.
1 parent ac13edc commit 76750df

File tree

16 files changed

+399
-437
lines changed

16 files changed

+399
-437
lines changed

src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs

Lines changed: 172 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,77 @@ public static partial class Monitor
3030
**
3131
** Exceptions: ArgumentNullException if object is null.
3232
=========================================================================*/
33+
public static void Enter(object obj)
34+
{
35+
ArgumentNullException.ThrowIfNull(obj, null);
36+
37+
if (!TryEnter_FastPath(obj))
38+
{
39+
Enter_Slowpath(obj);
40+
}
41+
}
42+
3343
[MethodImpl(MethodImplOptions.InternalCall)]
34-
public static extern void Enter(object obj);
44+
private static extern bool TryEnter_FastPath(object obj);
45+
46+
// These must match the values in syncblk.h
47+
private enum EnterHelperResult
48+
{
49+
Contention = 0,
50+
Entered = 1,
51+
UseSlowPath = 2
52+
}
53+
54+
// These must match the values in syncblk.h
55+
private enum LeaveHelperAction
56+
{
57+
None = 0,
58+
Signal = 1,
59+
Yield = 2,
60+
Contention = 3,
61+
Error = 4,
62+
};
63+
64+
[MethodImpl(MethodImplOptions.InternalCall)]
65+
private static extern EnterHelperResult TryEnter_FastPath_WithTimeout(object obj, int timeout);
66+
67+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Monitor_Enter_Slowpath")]
68+
private static partial void Enter_Slowpath(ObjectHandleOnStack obj);
69+
70+
[MethodImpl(MethodImplOptions.NoInlining)]
71+
private static void Enter_Slowpath(object obj)
72+
{
73+
Enter_Slowpath(ObjectHandleOnStack.Create(ref obj));
74+
}
3575

76+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Monitor_TryEnter_Slowpath")]
77+
private static partial int TryEnter_Slowpath(ObjectHandleOnStack obj, int timeout);
78+
79+
[MethodImpl(MethodImplOptions.NoInlining)]
80+
private static bool TryEnter_Slowpath(object obj)
81+
{
82+
if (TryEnter_Slowpath(ObjectHandleOnStack.Create(ref obj), 0) != 0)
83+
{
84+
return true;
85+
}
86+
else
87+
{
88+
return false;
89+
}
90+
}
91+
92+
[MethodImpl(MethodImplOptions.NoInlining)]
93+
private static bool TryEnter_Slowpath(object obj, int timeout)
94+
{
95+
if (TryEnter_Slowpath(ObjectHandleOnStack.Create(ref obj), timeout) != 0)
96+
{
97+
return true;
98+
}
99+
else
100+
{
101+
return false;
102+
}
103+
}
36104

37105
// Use a ref bool instead of out to ensure that unverifiable code must
38106
// initialize this value to something. If we used out, the value
@@ -44,7 +112,13 @@ public static void Enter(object obj, ref bool lockTaken)
44112
if (lockTaken)
45113
ThrowLockTakenException();
46114

47-
ReliableEnter(obj, ref lockTaken);
115+
ArgumentNullException.ThrowIfNull(obj, null);
116+
117+
if (!TryEnter_FastPath(obj))
118+
{
119+
Enter_Slowpath(obj);
120+
}
121+
lockTaken = true;
48122
Debug.Assert(lockTaken);
49123
}
50124

@@ -55,9 +129,16 @@ private static void ThrowLockTakenException()
55129
}
56130

57131
[MethodImpl(MethodImplOptions.InternalCall)]
58-
private static extern void ReliableEnter(object obj, ref bool lockTaken);
132+
private static extern LeaveHelperAction Exit_FastPath(object obj);
59133

134+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Monitor_Exit_Slowpath")]
135+
private static partial void Exit_Slowpath(ObjectHandleOnStack obj, LeaveHelperAction exitBehavior);
60136

137+
[MethodImpl(MethodImplOptions.NoInlining)]
138+
private static void Exit_Slowpath(LeaveHelperAction exitBehavior, object obj)
139+
{
140+
Exit_Slowpath(ObjectHandleOnStack.Create(ref obj), exitBehavior);
141+
}
61142

62143
/*=========================================================================
63144
** Release the monitor lock. If one or more threads are waiting to acquire the
@@ -68,8 +149,37 @@ private static void ThrowLockTakenException()
68149
** SynchronizationLockException if the current thread does not
69150
** own the lock.
70151
=========================================================================*/
71-
[MethodImpl(MethodImplOptions.InternalCall)]
72-
public static extern void Exit(object obj);
152+
public static void Exit(object obj)
153+
{
154+
ArgumentNullException.ThrowIfNull(obj, null);
155+
156+
LeaveHelperAction exitBehavior = Exit_FastPath(obj);
157+
158+
if (exitBehavior == LeaveHelperAction.None)
159+
return;
160+
161+
Exit_Slowpath(exitBehavior, obj);
162+
}
163+
164+
// Used to implement synchronized methods on non Windows-X86 architectures
165+
internal static void ExitIfLockTaken(object obj, ref bool lockTaken)
166+
{
167+
ArgumentNullException.ThrowIfNull(obj, null);
168+
169+
if (lockTaken)
170+
{
171+
LeaveHelperAction exitBehavior = Exit_FastPath(obj);
172+
173+
if (exitBehavior == LeaveHelperAction.None)
174+
{
175+
lockTaken = false;
176+
return;
177+
}
178+
179+
Exit_Slowpath(exitBehavior, obj);
180+
lockTaken = false;
181+
}
182+
}
73183

74184
/*=========================================================================
75185
** Similar to Enter, but will never block. That is, if the current thread can
@@ -80,9 +190,41 @@ private static void ThrowLockTakenException()
80190
=========================================================================*/
81191
public static bool TryEnter(object obj)
82192
{
83-
bool lockTaken = false;
84-
TryEnter(obj, 0, ref lockTaken);
85-
return lockTaken;
193+
ArgumentNullException.ThrowIfNull(obj, null);
194+
195+
EnterHelperResult tryEnterResult = TryEnter_FastPath_WithTimeout(obj, 0);
196+
if (tryEnterResult == EnterHelperResult.Entered)
197+
{
198+
return true;
199+
}
200+
else if (tryEnterResult == EnterHelperResult.Contention)
201+
{
202+
return false;
203+
}
204+
205+
return TryEnter_Slowpath(obj);
206+
}
207+
208+
private static void TryEnter_Timeout_WithLockTaken(object obj, int millisecondsTimeout, ref bool lockTaken)
209+
{
210+
if (millisecondsTimeout >= -1)
211+
{
212+
EnterHelperResult tryEnterResult = TryEnter_FastPath_WithTimeout(obj, millisecondsTimeout);
213+
if (tryEnterResult == EnterHelperResult.Entered)
214+
{
215+
lockTaken = true;
216+
return;
217+
}
218+
else if (millisecondsTimeout == 0 && (tryEnterResult == EnterHelperResult.Contention))
219+
{
220+
return;
221+
}
222+
}
223+
224+
if (TryEnter_Slowpath(obj, millisecondsTimeout))
225+
{
226+
lockTaken = true;
227+
}
86228
}
87229

88230
// The JIT should inline this method to allow check of lockTaken argument to be optimized out
@@ -92,7 +234,9 @@ public static void TryEnter(object obj, ref bool lockTaken)
92234
if (lockTaken)
93235
ThrowLockTakenException();
94236

95-
ReliableEnterTimeout(obj, 0, ref lockTaken);
237+
ArgumentNullException.ThrowIfNull(obj, null);
238+
239+
TryEnter_Timeout_WithLockTaken(obj, 0, ref lockTaken);
96240
}
97241

98242
/*=========================================================================
@@ -103,13 +247,24 @@ public static void TryEnter(object obj, ref bool lockTaken)
103247
** Exceptions: ArgumentNullException if object is null.
104248
** ArgumentException if timeout < -1 (Timeout.Infinite).
105249
=========================================================================*/
106-
// The JIT should inline this method to allow check of lockTaken argument to be optimized out
107-
// in the typical case. Note that the method has to be transparent for inlining to be allowed by the VM.
108250
public static bool TryEnter(object obj, int millisecondsTimeout)
109251
{
110-
bool lockTaken = false;
111-
TryEnter(obj, millisecondsTimeout, ref lockTaken);
112-
return lockTaken;
252+
ArgumentNullException.ThrowIfNull(obj, null);
253+
254+
if (millisecondsTimeout >= -1)
255+
{
256+
EnterHelperResult tryEnterResult = TryEnter_FastPath_WithTimeout(obj, millisecondsTimeout);
257+
if (tryEnterResult == EnterHelperResult.Entered)
258+
{
259+
return true;
260+
}
261+
else if (millisecondsTimeout == 0 && (tryEnterResult == EnterHelperResult.Contention))
262+
{
263+
return false;
264+
}
265+
}
266+
267+
return TryEnter_Slowpath(obj, millisecondsTimeout);
113268
}
114269

115270
// The JIT should inline this method to allow check of lockTaken argument to be optimized out
@@ -119,11 +274,10 @@ public static void TryEnter(object obj, int millisecondsTimeout, ref bool lockTa
119274
if (lockTaken)
120275
ThrowLockTakenException();
121276

122-
ReliableEnterTimeout(obj, millisecondsTimeout, ref lockTaken);
123-
}
277+
ArgumentNullException.ThrowIfNull(obj, null);
124278

125-
[MethodImpl(MethodImplOptions.InternalCall)]
126-
private static extern void ReliableEnterTimeout(object obj, int timeout, ref bool lockTaken);
279+
TryEnter_Timeout_WithLockTaken(obj, millisecondsTimeout, ref lockTaken);
280+
}
127281

128282
public static bool IsEntered(object obj)
129283
{

src/coreclr/classlibnative/bcltype/objectnative.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,125 @@ extern "C" INT64 QCALLTYPE Monitor_GetLockContentionCount()
197197
END_QCALL;
198198
return result;
199199
}
200+
201+
//========================================================================
202+
//
203+
// MONITOR HELPERS
204+
//
205+
//========================================================================
206+
207+
/*********************************************************************/
208+
extern "C" void QCALLTYPE Monitor_Enter_Slowpath(QCall::ObjectHandleOnStack objHandle)
209+
{
210+
QCALL_CONTRACT;
211+
212+
BEGIN_QCALL;
213+
214+
GCX_COOP();
215+
216+
objHandle.Get()->EnterObjMonitor();
217+
END_QCALL;
218+
}
219+
220+
/*********************************************************************/
221+
#include <optsmallperfcritical.h>
222+
223+
FCIMPL1(FC_BOOL_RET, ObjectNative::Monitor_TryEnter_FastPath, Object* obj)
224+
{
225+
FCALL_CONTRACT;
226+
227+
if (obj->TryEnterObjMonitorSpinHelper())
228+
{
229+
FC_RETURN_BOOL(TRUE);
230+
}
231+
else
232+
{
233+
FC_RETURN_BOOL(FALSE);
234+
}
235+
}
236+
FCIMPLEND
237+
238+
FCIMPL2(AwareLock::EnterHelperResult, ObjectNative::Monitor_TryEnter_FastPath_WithTimeout, Object* obj, INT32 timeOut)
239+
{
240+
FCALL_CONTRACT;
241+
242+
Thread* pCurThread = GetThread();
243+
244+
if (pCurThread->CatchAtSafePoint())
245+
{
246+
return AwareLock::EnterHelperResult::UseSlowPath;
247+
}
248+
249+
AwareLock::EnterHelperResult result = obj->EnterObjMonitorHelper(pCurThread);
250+
if (result == AwareLock::EnterHelperResult::Contention)
251+
{
252+
if (timeOut == 0)
253+
{
254+
return AwareLock::EnterHelperResult::Contention;
255+
}
256+
257+
result = obj->EnterObjMonitorHelperSpin(pCurThread);
258+
}
259+
260+
return result;
261+
}
262+
FCIMPLEND
263+
264+
#include <optdefault.h>
265+
266+
/*********************************************************************/
267+
extern "C" INT32 QCALLTYPE Monitor_TryEnter_Slowpath(QCall::ObjectHandleOnStack objHandle, INT32 timeOut)
268+
{
269+
QCALL_CONTRACT;
270+
271+
BOOL result = FALSE;
272+
273+
BEGIN_QCALL;
274+
275+
GCX_COOP();
276+
277+
if (timeOut < -1)
278+
COMPlusThrow(kArgumentOutOfRangeException);
279+
280+
result = objHandle.Get()->TryEnterObjMonitor(timeOut);
281+
282+
END_QCALL;
283+
284+
return result;
285+
}
286+
287+
/*********************************************************************/
288+
extern "C" void QCALLTYPE Monitor_Exit_Slowpath(QCall::ObjectHandleOnStack objHandle, AwareLock::LeaveHelperAction exitBehavior)
289+
{
290+
QCALL_CONTRACT;
291+
292+
BEGIN_QCALL;
293+
294+
GCX_COOP();
295+
296+
if (exitBehavior != AwareLock::LeaveHelperAction::Signal)
297+
{
298+
if (!objHandle.Get()->LeaveObjMonitor())
299+
COMPlusThrow(kSynchronizationLockException);
300+
}
301+
else
302+
{
303+
// Signal the event
304+
SyncBlock *psb = objHandle.Get()->PassiveGetSyncBlock();
305+
if (psb != NULL)
306+
psb->QuickGetMonitor()->Signal();
307+
}
308+
END_QCALL;
309+
}
310+
311+
#include <optsmallperfcritical.h>
312+
FCIMPL1(AwareLock::LeaveHelperAction, ObjectNative::Monitor_Exit_FastPath, Object* obj)
313+
{
314+
FCALL_CONTRACT;
315+
316+
// Handle the simple case without erecting helper frame
317+
return obj->LeaveObjMonitorHelper(GetThread());
318+
}
319+
FCIMPLEND
320+
#include <optdefault.h>
321+

src/coreclr/classlibnative/bcltype/objectnative.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class ObjectNative
2727
static FCDECL1(INT32, TryGetHashCode, Object* vThisRef);
2828
static FCDECL2(FC_BOOL_RET, ContentEquals, Object *pThisRef, Object *pCompareRef);
2929
static FCDECL1(FC_BOOL_RET, IsLockHeld, Object* pThisUNSAFE);
30+
31+
static FCDECL1(FC_BOOL_RET, Monitor_TryEnter_FastPath, Object* obj);
32+
static FCDECL2(AwareLock::EnterHelperResult, Monitor_TryEnter_FastPath_WithTimeout, Object* obj, INT32 timeout);
33+
static FCDECL1(AwareLock::LeaveHelperAction, Monitor_Exit_FastPath, Object* obj);
3034
};
3135

3236
extern "C" INT32 QCALLTYPE ObjectNative_GetHashCodeSlow(QCall::ObjectHandleOnStack objHandle);
@@ -35,5 +39,9 @@ extern "C" BOOL QCALLTYPE Monitor_Wait(QCall::ObjectHandleOnStack pThis, INT32 T
3539
extern "C" void QCALLTYPE Monitor_Pulse(QCall::ObjectHandleOnStack pThis);
3640
extern "C" void QCALLTYPE Monitor_PulseAll(QCall::ObjectHandleOnStack pThis);
3741
extern "C" INT64 QCALLTYPE Monitor_GetLockContentionCount();
42+
extern "C" void QCALLTYPE Monitor_Enter_Slowpath(QCall::ObjectHandleOnStack objHandle);
43+
extern "C" void QCALLTYPE Monitor_Exit_Slowpath(QCall::ObjectHandleOnStack objHandle, AwareLock::LeaveHelperAction exitBehavior);
44+
extern "C" INT32 QCALLTYPE Monitor_TryEnter_Slowpath(QCall::ObjectHandleOnStack objHandle, INT32 timeOut);
45+
3846

3947
#endif // _OBJECTNATIVE_H_

0 commit comments

Comments
 (0)