diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
index db2a7d21781a7..c40c41e3aa09a 100644
--- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -266,6 +266,7 @@
+
diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
index 0f590d3492d05..732ebb26d8922 100644
--- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
@@ -39,8 +39,12 @@ public static int OffsetToStringData
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern int InternalGetHashCode(object? o);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetHashCode(object? o)
{
+ // NOTE: the interpreter does not run this code. It intrinsifies the whole RuntimeHelpers.GetHashCode function
+ if (Threading.ObjectHeader.TryGetHashCode (o, out int hash))
+ return hash;
return InternalGetHashCode(o);
}
@@ -55,8 +59,12 @@ public static int GetHashCode(object? o)
/// The advantage of this over is that it avoids assigning a hash
/// code to the object if it does not already have one.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int TryGetHashCode(object? o)
{
+ // NOTE: the interpreter does not run this code. It intrinsifies the whole RuntimeHelpers.TryGetHashCode function
+ if (Threading.ObjectHeader.TryGetHashCode (o, out int hash))
+ return hash;
return InternalTryGetHashCode(o);
}
diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs
index e004a373ee771..96424ec2bffa9 100644
--- a/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Threading/Monitor.Mono.cs
@@ -23,7 +23,17 @@ public static void Enter(object obj, ref bool lockTaken)
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- public static extern void Exit(object obj);
+ private static extern void InternalExit(object obj);
+
+ public static void Exit(object obj)
+ {
+ if (obj == null)
+ ArgumentNullException.ThrowIfNull(obj);
+ if (ObjectHeader.TryExitChecked(obj))
+ return;
+
+ InternalExit(obj);
+ }
public static bool TryEnter(object obj)
{
@@ -57,7 +67,7 @@ public static void TryEnter(object obj, int millisecondsTimeout, ref bool lockTa
public static bool IsEntered(object obj)
{
ArgumentNullException.ThrowIfNull(obj);
- return IsEnteredNative(obj);
+ return ObjectHeader.IsEntered(obj);
}
[UnsupportedOSPlatform("browser")]
@@ -79,15 +89,12 @@ public static void PulseAll(object obj)
ObjPulseAll(obj);
}
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- private static extern bool Monitor_test_synchronised(object obj);
-
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void Monitor_pulse(object obj);
private static void ObjPulse(object obj)
{
- if (!Monitor_test_synchronised(obj))
+ if (!ObjectHeader.HasOwner(obj))
throw new SynchronizationLockException();
Monitor_pulse(obj);
@@ -98,7 +105,7 @@ private static void ObjPulse(object obj)
private static void ObjPulseAll(object obj)
{
- if (!Monitor_test_synchronised(obj))
+ if (!ObjectHeader.HasOwner(obj))
throw new SynchronizationLockException();
Monitor_pulse_all(obj);
@@ -111,7 +118,7 @@ private static bool ObjWait(int millisecondsTimeout, object obj)
{
if (millisecondsTimeout < 0 && millisecondsTimeout != (int)Timeout.Infinite)
throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));
- if (!Monitor_test_synchronised(obj))
+ if (!ObjectHeader.HasOwner(obj))
throw new SynchronizationLockException();
return Monitor_wait(obj, millisecondsTimeout, true);
@@ -120,22 +127,22 @@ private static bool ObjWait(int millisecondsTimeout, object obj)
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void try_enter_with_atomic_var(object obj, int millisecondsTimeout, bool allowInterruption, ref bool lockTaken);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReliableEnterTimeout(object obj, int timeout, ref bool lockTaken)
{
- ArgumentNullException.ThrowIfNull(obj);
+ if (obj == null)
+ ArgumentNullException.ThrowIfNull(obj);
if (timeout < 0 && timeout != (int)Timeout.Infinite)
throw new ArgumentOutOfRangeException(nameof(timeout));
- try_enter_with_atomic_var(obj, timeout, true, ref lockTaken);
- }
-
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- private static extern bool Monitor_test_owner(object obj);
+ // fast path
+ if (ObjectHeader.TryEnterFast(obj)) {
+ lockTaken = true;
+ return;
+ }
- private static bool IsEnteredNative(object obj)
- {
- return Monitor_test_owner(obj);
+ try_enter_with_atomic_var(obj, timeout, true, ref lockTaken);
}
public static extern long LockContentionCount
diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/ObjectHeader.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/ObjectHeader.Mono.cs
new file mode 100644
index 0000000000000..16eccb9511c57
--- /dev/null
+++ b/src/mono/System.Private.CoreLib/src/System/Threading/ObjectHeader.Mono.cs
@@ -0,0 +1,451 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Threading;
+
+///
+/// Manipulates the object header located in the first few words of each object in the managed heap.
+///
+internal static class ObjectHeader
+{
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct Header
+ {
+#region Keep in sync with src/native/public/mono/metadata/details/object-types.h
+ public void* vtable;
+ public IntPtr synchronization; // really a LockWord
+#endregion // keep in sync with src/native/public/mono/metadata/details/object-types.h
+ }
+
+ // This is similar to QCallHandler ObjectHandleOnStack, but with a getter that let's view the
+ // object's header. This does two things:
+ //
+ // 1. It gives us a way to pass around a reference to the object header
+ //
+ // 2. because mono uses conservative stack scanning, we ensure there's always some place on the
+ // stack that stores a pointer to the object, thus pinning the object.
+ private unsafe ref struct ObjectHeaderOnStack
+ {
+ private Header** _header;
+ private ObjectHeaderOnStack(ref object o)
+ {
+ _header = (Header**)Unsafe.AsPointer(ref o);
+ }
+ public static ObjectHeaderOnStack Create(ref object o)
+ {
+ return new ObjectHeaderOnStack(ref o);
+ }
+ public ref Header Header => ref Unsafe.AsRef(*_header);
+
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private unsafe struct MonoThreadsSync
+ {
+#region Keep in sync with monitor.h
+ // FIXME: volatile?
+ public uint status;
+ public uint nest;
+ public int hash_code;
+ // Note: more fields after here
+#endregion // keep in sync with monitor.h
+ }
+
+ private static class SyncBlock
+ {
+ public static int HashCode(ref MonoThreadsSync mon) => mon.hash_code;
+ public static ref uint Status (ref MonoThreadsSync mon) => ref mon.status;
+
+ // only call if current thread owns the lock
+ public static void IncrementNest (ref MonoThreadsSync mon)
+ {
+ mon.nest++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryDecrementNest(ref MonoThreadsSync mon)
+ {
+ Debug.Assert (mon.nest > 0);
+ if (mon.nest == 1)
+ {
+ // leave mon.nest == 1, the caller will set mon.owner to 0 to indicate the monitor
+ // is unlocked. nest will start from 1 for the next time it is entered.
+ return false;
+ }
+ mon.nest--;
+ return true;
+ }
+ }
+
+ //
+ // Manipulate the MonoThreadSync:status field
+ //
+ private static class MonitorStatus
+ {
+#region Keep in sync with monitor.h
+ private const uint OwnerMask = 0x0000ffffu;
+ private const uint EntryCountMask = 0xffff0000u;
+ private const uint EntryCountWaiters = 0x80000000u;
+ //private const uint EntryCountZero = 0x7fff0000u;
+ //private const int EntryCountShift = 16;
+#endregion // keep in sync with monitor.h
+
+ public static int GetOwner(uint status) => (int)(status & OwnerMask);
+ public static uint SetOwner (uint status, int owner)
+ {
+ return (status & EntryCountMask) | (uint)owner;
+ }
+
+ public static bool HaveWaiters(uint status) => (status & EntryCountWaiters) != 0;
+ }
+
+ //
+ // A union that contains either an uninflated lockword, or a pointer to a synchronization struct
+ //
+ [StructLayout(LayoutKind.Sequential)]
+ private struct LockWord
+ {
+#region Keep in sync with monitor.h
+
+ private IntPtr _lock_word;
+
+ private const int StatusBits = 2;
+
+ private const int NestBits = 8;
+
+ private const IntPtr StatusMask = (1 << StatusBits) - 1;
+
+ private const IntPtr NestMask = ((1 << NestBits) - 1) << StatusBits;
+
+ private const int HashShift = StatusBits;
+
+ private const int NestShift = StatusBits;
+
+ private const int OwnerShift = StatusBits + NestBits;
+
+ [Flags]
+ private enum Status
+ {
+ Flat = 0,
+ HasHash = 1,
+ Inflated = 2,
+ }
+#endregion // Keep in sync with monitor.h
+
+ public bool IsInflated => (_lock_word & (IntPtr)Status.Inflated) != 0;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref MonoThreadsSync GetInflatedLock()
+ {
+ unsafe
+ {
+ IntPtr ptr = _lock_word & ~StatusMask;
+ return ref Unsafe.AsRef((void*)ptr);
+ }
+ }
+
+ public bool HasHash => (_lock_word & (IntPtr)Status.HasHash) != 0;
+
+ public bool IsFree => _lock_word == IntPtr.Zero;
+
+ public bool IsFlat => (_lock_word & StatusMask) == (IntPtr)Status.Flat;
+
+ public bool IsNested => (_lock_word & NestMask) != 0;
+
+ public int FlatHash => (int)(_lock_word >>> HashShift);
+
+ public int FlatNest
+ {
+ get
+ {
+ if (IsFree)
+ return 0;
+ /* Inword nest count starts from 0 */
+ return 1 + (int)((_lock_word & NestMask) >>> NestShift);
+ }
+ }
+
+ public bool IsNestMax => (_lock_word & NestMask) == NestMask;
+
+ public LockWord IncrementNest()
+ {
+ LockWord res;
+ res._lock_word = _lock_word + (1 << NestShift);
+ return res;
+ }
+
+ public LockWord DecrementNest()
+ {
+ LockWord res;
+ res._lock_word = _lock_word - (1 << NestShift);
+ return res;
+ }
+
+ public int GetOwner() => (int)(_lock_word >>> OwnerShift);
+
+ public static LockWord NewThinHash(int hash)
+ {
+ LockWord res;
+ res._lock_word = (((IntPtr)(uint)hash) << HashShift) | (IntPtr)Status.HasHash;
+ return res;
+ }
+
+ public static unsafe LockWord NewInflated(MonoThreadsSync* sync)
+ {
+ IntPtr ptr = (IntPtr)(void*)sync;
+ ptr |= (IntPtr)Status.Inflated;
+ LockWord res;
+ res._lock_word = ptr;
+ return res;
+ }
+
+ public static LockWord NewFlat(int owner)
+ {
+ LockWord res;
+ res._lock_word = ((IntPtr)(uint)owner) << OwnerShift;
+ return res;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static LockWord FromObjectHeader(ref Header header)
+ {
+ LockWord lw;
+ lw._lock_word = header.synchronization;
+ return lw;
+ }
+
+ public IntPtr AsIntPtr => _lock_word;
+
+ internal void SetFromIntPtr (IntPtr new_lw)
+ {
+ _lock_word = new_lw;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static LockWord GetLockWord(ObjectHeaderOnStack h)
+ {
+ return LockWord.FromObjectHeader(ref h.Header);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static IntPtr LockWordCompareExchange (ObjectHeaderOnStack h, LockWord nlw, LockWord expected)
+ {
+ return Interlocked.CompareExchange (ref h.Header.synchronization, nlw.AsIntPtr, expected.AsIntPtr);
+ }
+
+ ///
+ /// Tries to get the hash code from the object if it is
+ /// already known and return true. Otherwise returns false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryGetHashCode(object? o, out int hash)
+ {
+ hash = 0;
+ if (o == null)
+ return true;
+
+ ObjectHeaderOnStack h = ObjectHeaderOnStack.Create(ref o);
+ LockWord lw = GetLockWord (h);
+ if (lw.HasHash) {
+ if (lw.IsInflated) {
+ ref MonoThreadsSync mon = ref lw.GetInflatedLock();
+ hash = SyncBlock.HashCode(ref mon);
+ return false;
+ } else {
+ hash = lw.FlatHash;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryEnterInflatedFast(scoped ref MonoThreadsSync mon, int small_id)
+ {
+
+ uint old_status = SyncBlock.Status (ref mon);
+ if (MonitorStatus.GetOwner(old_status) == 0)
+ {
+ uint new_status = MonitorStatus.SetOwner(old_status, small_id);
+ uint prev_status = Interlocked.CompareExchange (ref SyncBlock.Status (ref mon), new_status, old_status);
+ if (prev_status == old_status)
+ {
+ return true;
+ }
+ // someone else changed the status, fall back to the slow path
+ return false;
+ }
+ if (MonitorStatus.GetOwner(old_status) == small_id)
+ {
+ // we own it
+ SyncBlock.IncrementNest (ref mon);
+ return true;
+ }
+ else
+ {
+ // someone else owns it, fall back to slow path
+ return false;
+ }
+ }
+
+ // returns false if we should fall back to the slow path
+ // returns true if the lock was taken
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryEnterFast(object? o)
+ {
+ Debug.Assert (o != null);
+ ObjectHeaderOnStack h = ObjectHeaderOnStack.Create (ref o);
+ LockWord lw = GetLockWord (h);
+ if (!lw.IsInflated && lw.HasHash)
+ return false; // need to inflate, fall back to native
+ int owner = Thread.CurrentThread.GetSmallId();
+ if (lw.IsFree)
+ {
+ LockWord nlw = LockWord.NewFlat(owner);
+ if (LockWordCompareExchange (h, nlw, lw) == lw.AsIntPtr)
+ {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ else if (lw.IsInflated)
+ {
+ return TryEnterInflatedFast(ref lw.GetInflatedLock(), owner);
+ }
+ else if (lw.IsFlat)
+ {
+ if (lw.GetOwner() == owner)
+ {
+ if (lw.IsNestMax)
+ {
+ // too much recursive locking, need to inflate
+ return false;
+ } else {
+ LockWord nlw = lw.IncrementNest();
+ if (LockWordCompareExchange (h, nlw, lw) == lw.AsIntPtr)
+ {
+ return true;
+ }
+ else
+ {
+ // someone else inflated it in the meantime, fall back to slow path
+ return false;
+ }
+ }
+ }
+ // there's contention, go to slow path
+ return false;
+ }
+ Debug.Assert (lw.HasHash);
+ return false;
+ }
+
+ // true if obj is owned by the current thread
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsEntered(object obj)
+ {
+ ObjectHeaderOnStack h = ObjectHeaderOnStack.Create(ref obj);
+ LockWord lw = GetLockWord(h);
+
+ if (lw.IsFlat)
+ {
+ return lw.GetOwner() == Thread.CurrentThread.GetSmallId();
+ }
+ else if (lw.IsInflated)
+ {
+ return MonitorStatus.GetOwner(lw.GetInflatedLock().status) == Thread.CurrentThread.GetSmallId();
+ }
+ return false;
+ }
+
+ // true if obj is owned by any thread
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasOwner(object obj)
+ {
+ ObjectHeaderOnStack h = ObjectHeaderOnStack.Create(ref obj);
+ LockWord lw = GetLockWord(h);
+
+ if (lw.IsFlat)
+ return !lw.IsFree;
+ else if (lw.IsInflated)
+ {
+ return MonitorStatus.GetOwner(lw.GetInflatedLock().status) != 0;
+ }
+
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryExitInflated(scoped ref MonoThreadsSync mon)
+ {
+ // if we're in a nested lock, decrement the count and we're done
+ if (SyncBlock.TryDecrementNest (ref mon))
+ return true;
+
+ ref uint status = ref SyncBlock.Status (ref mon);
+ uint old_status = status;
+ // if there are waiters, fall back to the slow path to wake them
+ if (MonitorStatus.HaveWaiters (old_status))
+ return false;
+ uint new_status = MonitorStatus.SetOwner (old_status, 0);
+ uint prev_status = Interlocked.CompareExchange (ref status, new_status, old_status);
+ if (prev_status == old_status)
+ return true; // success, and there were no waiters, we're done
+ else
+ return false; // we need to retry, but maybe a waiter arrived, fall back to the slow path
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryExitFlat(ObjectHeaderOnStack h, LockWord lw)
+ {
+
+ Debug.Assert (!lw.IsInflated);
+ // if the lock word is flat, there has been no contention
+ LockWord nlw;
+ if (lw.IsNested)
+ nlw = lw.DecrementNest();
+ else
+ nlw = default;
+
+ if (LockWordCompareExchange (h, nlw, lw) == lw.AsIntPtr)
+ return true;
+ // someone inflated the lock in the meantime, fall back to the slow path
+
+ return false;
+ }
+
+
+ // checks that obj is locked by the current thread
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ #pragma warning disable IDE0060 // spurious "unused parameter" warning because we never use obj, just take aref to it.
+ public static bool TryExitChecked(object obj)
+ #pragma warning restore IDE0060
+ {
+ ObjectHeaderOnStack h = ObjectHeaderOnStack.Create(ref obj);
+ LockWord lw = GetLockWord(h);
+ bool owned = false;
+
+ ref MonoThreadsSync mon = ref Unsafe.NullRef();
+ if (lw.IsFlat)
+ {
+ owned = (lw.GetOwner() == Thread.CurrentThread.GetSmallId());
+ }
+ else if (lw.IsInflated)
+ {
+ mon = ref lw.GetInflatedLock();
+ owned = (MonitorStatus.GetOwner(mon.status) == Thread.CurrentThread.GetSmallId());
+ }
+ if (!owned)
+ throw new SynchronizationLockException(SR.Arg_SynchronizationLockException);
+ if (lw.IsInflated)
+ return TryExitInflated(ref mon);
+ else
+ return TryExitFlat(h, lw);
+ }
+}
diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs
index 528ad1ab12be2..31bc824008607 100644
--- a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs
@@ -350,5 +350,7 @@ private static void SpinWait_nop()
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void SetPriority(Thread thread, int priority);
+
+ internal int GetSmallId() => small_id;
}
}
diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h
index 813fbe3571182..464b708b6e397 100644
--- a/src/mono/mono/metadata/icall-def.h
+++ b/src/mono/mono/metadata/icall-def.h
@@ -576,11 +576,9 @@ NOHANDLES(ICALL(LIFOSEM_4, "TimedWaitInternal", ves_icall_System_Threading_LowLe
ICALL_TYPE(MONIT, "System.Threading.Monitor", MONIT_0)
HANDLES(MONIT_0, "Enter", ves_icall_System_Threading_Monitor_Monitor_Enter, void, 1, (MonoObject))
-HANDLES(MONIT_1, "Exit", mono_monitor_exit_icall, void, 1, (MonoObject))
+HANDLES(MONIT_1, "InternalExit", mono_monitor_exit_icall, void, 1, (MonoObject))
HANDLES(MONIT_2, "Monitor_pulse", ves_icall_System_Threading_Monitor_Monitor_pulse, void, 1, (MonoObject))
HANDLES(MONIT_3, "Monitor_pulse_all", ves_icall_System_Threading_Monitor_Monitor_pulse_all, void, 1, (MonoObject))
-HANDLES(MONIT_4, "Monitor_test_owner", ves_icall_System_Threading_Monitor_Monitor_test_owner, MonoBoolean, 1, (MonoObject))
-HANDLES(MONIT_5, "Monitor_test_synchronised", ves_icall_System_Threading_Monitor_Monitor_test_synchronised, MonoBoolean, 1, (MonoObject))
HANDLES(MONIT_7, "Monitor_wait", ves_icall_System_Threading_Monitor_Monitor_wait, MonoBoolean, 3, (MonoObject, guint32, MonoBoolean))
NOHANDLES(ICALL(MONIT_8, "get_LockContentionCount", ves_icall_System_Threading_Monitor_Monitor_LockContentionCount))
HANDLES(MONIT_9, "try_enter_with_atomic_var", ves_icall_System_Threading_Monitor_Monitor_try_enter_with_atomic_var, void, 4, (MonoObject, guint32, MonoBoolean, MonoBoolean_ref))
diff --git a/src/mono/mono/metadata/monitor.c b/src/mono/mono/metadata/monitor.c
index 322ff8459cb3b..13499b0de8b8e 100644
--- a/src/mono/mono/metadata/monitor.c
+++ b/src/mono/mono/metadata/monitor.c
@@ -1247,46 +1247,6 @@ mono_monitor_enter_v4_fast (MonoObject *obj, MonoBoolean *lock_taken)
return (guint32)res;
}
-MonoBoolean
-ves_icall_System_Threading_Monitor_Monitor_test_owner (MonoObjectHandle obj_handle, MonoError* error)
-{
- MonoObject* const obj = MONO_HANDLE_RAW (obj_handle);
-
- LockWord lw;
-
- LOCK_DEBUG (g_message ("%s: Testing if %p is owned by thread %d", __func__, obj, mono_thread_info_get_small_id()));
-
- lw.sync = obj->synchronisation;
-
- if (lock_word_is_flat (lw)) {
- return lock_word_get_owner (lw) == mono_thread_info_get_small_id ();
- } else if (lock_word_is_inflated (lw)) {
- return mon_status_get_owner (lock_word_get_inflated_lock (lw)->status) == mono_thread_info_get_small_id ();
- }
-
- return FALSE;
-}
-
-MonoBoolean
-ves_icall_System_Threading_Monitor_Monitor_test_synchronised (MonoObjectHandle obj_handle, MonoError* error)
-{
- MonoObject* const obj = MONO_HANDLE_RAW (obj_handle);
-
- LockWord lw;
-
- LOCK_DEBUG (g_message("%s: (%d) Testing if %p is owned by any thread", __func__, mono_thread_info_get_small_id (), obj));
-
- lw.sync = obj->synchronisation;
-
- if (lock_word_is_flat (lw)) {
- return !lock_word_is_free (lw);
- } else if (lock_word_is_inflated (lw)) {
- return mon_status_get_owner (lock_word_get_inflated_lock (lw)->status) != 0;
- }
-
- return FALSE;
-}
-
/* All wait list manipulation in the pulse, pulseall and wait
* functions happens while the monitor lock is held, so we don't need
* any extra struct locking
diff --git a/src/mono/mono/metadata/native-library.c b/src/mono/mono/metadata/native-library.c
index 164a3aef132f2..a519620215930 100644
--- a/src/mono/mono/metadata/native-library.c
+++ b/src/mono/mono/metadata/native-library.c
@@ -706,6 +706,16 @@ netcore_resolve_with_load (MonoAssemblyLoadContext *alc, const char *scope, Mono
if (mono_runtime_get_no_exec ())
return NULL;
+ /* default ALC LoadUnmanagedDll always returns null */
+ /* NOTE: This is more than an optimization. It allows us to avoid triggering creation of
+ * the managed object for the Default ALC while we're looking up icalls for Monitor. The
+ * AssemblyLoadContext base constructor uses a lock on the allContexts variable which leads
+ * to recursive static constructor invocation which may show unexpected side effects ---
+ * such as a null AssemblyLoadContext.Default --- early in the runtime.
+ */
+ if (mono_alc_is_default (alc))
+ return NULL;
+
HANDLE_FUNCTION_ENTER ();
MonoStringHandle scope_handle;
diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c
index ab5b9d7281eff..41efe38627f38 100644
--- a/src/mono/mono/mini/interp/interp.c
+++ b/src/mono/mono/mini/interp/interp.c
@@ -7007,6 +7007,41 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK;
ip += 4;
MINT_IN_BREAK;
}
+ MINT_IN_CASE(MINT_MONO_CMPXCHG_I4) {
+ gint32 *dest = LOCAL_VAR(ip[2], gint32*);
+ gint32 value = LOCAL_VAR(ip[3], gint32);
+ gint32 comparand = LOCAL_VAR(ip[4], gint32);
+ NULL_CHECK(dest);
+
+ LOCAL_VAR(ip[1], gint32) = mono_atomic_cas_i32(dest, value, comparand);
+ ip += 5;
+ MINT_IN_BREAK;
+ }
+ MINT_IN_CASE(MINT_MONO_CMPXCHG_I8) {
+ gboolean flag = FALSE;
+ gint64 *dest = LOCAL_VAR(ip[2], gint64*);
+ gint64 value = LOCAL_VAR(ip[3], gint64);
+ gint64 comparand = LOCAL_VAR(ip[4], gint64);
+ NULL_CHECK(dest);
+
+#if SIZEOF_VOID_P == 4
+ if (G_UNLIKELY ((size_t)dest & 0x7)) {
+ gint64 old;
+ mono_interlocked_lock ();
+ old = *dest;
+ if (old == comparand)
+ *dest = value;
+ mono_interlocked_unlock ();
+ LOCAL_VAR(ip[1], gint64) = old;
+ flag = TRUE;
+ }
+#endif
+
+ if (!flag)
+ LOCAL_VAR(ip[1], gint64) = mono_atomic_cas_i64(dest, value, comparand);
+ ip += 5;
+ MINT_IN_BREAK;
+ }
MINT_IN_CASE(MINT_MONO_LDDOMAIN)
LOCAL_VAR (ip [1], gpointer) = mono_domain_get ();
ip += 2;
diff --git a/src/mono/mono/mini/interp/jiterpreter.c b/src/mono/mono/mini/interp/jiterpreter.c
index 4b7647790d412..cef0a335eff47 100644
--- a/src/mono/mono/mini/interp/jiterpreter.c
+++ b/src/mono/mono/mini/interp/jiterpreter.c
@@ -657,6 +657,18 @@ mono_jiterp_interp_entry_prologue (JiterpEntryData *data, void *this_arg)
return sp_args;
}
+EMSCRIPTEN_KEEPALIVE int32_t
+mono_jiterp_cas_i32 (volatile int32_t *addr, int32_t newVal, int32_t expected)
+{
+ return mono_atomic_cas_i32 (addr, newVal, expected);
+}
+
+EMSCRIPTEN_KEEPALIVE void
+mono_jiterp_cas_i64 (volatile int64_t *addr, int64_t *newVal, int64_t *expected, int64_t *oldVal)
+{
+ *oldVal= mono_atomic_cas_i64 (addr, *newVal, *expected);
+}
+
// should_abort_trace returns one of these codes depending on the opcode and current state
#define TRACE_IGNORE -1
#define TRACE_CONTINUE 0
@@ -1098,7 +1110,6 @@ EMSCRIPTEN_KEEPALIVE int
mono_jiterp_get_hashcode (MonoObject ** ppObj)
{
MonoObject *obj = *ppObj;
- g_assert (obj);
return mono_object_hash_internal (obj);
}
@@ -1106,7 +1117,6 @@ EMSCRIPTEN_KEEPALIVE int
mono_jiterp_try_get_hashcode (MonoObject ** ppObj)
{
MonoObject *obj = *ppObj;
- g_assert (obj);
return mono_object_try_get_hash_internal (obj);
}
diff --git a/src/mono/mono/mini/interp/mintops.def b/src/mono/mono/mini/interp/mintops.def
index c0034017930f8..f7634ce9806e2 100644
--- a/src/mono/mono/mini/interp/mintops.def
+++ b/src/mono/mono/mini/interp/mintops.def
@@ -719,6 +719,8 @@ OPDEF(MINT_MONO_NEWOBJ, "mono_newobj", 3, 1, 0, MintOpClassToken)
OPDEF(MINT_MONO_RETOBJ, "mono_retobj", 2, 0, 1, MintOpNoArgs)
OPDEF(MINT_MONO_MEMORY_BARRIER, "mono_memory_barrier", 1, 0, 0, MintOpNoArgs)
OPDEF(MINT_MONO_EXCHANGE_I8, "mono_interlocked.xchg.i8", 4, 1, 2, MintOpNoArgs)
+OPDEF(MINT_MONO_CMPXCHG_I4, "mono_interlocked.cmpxchg.i4", 5, 1, 3, MintOpNoArgs)
+OPDEF(MINT_MONO_CMPXCHG_I8, "mono_interlocked.cmpxchg.i8", 5, 1, 3, MintOpNoArgs)
OPDEF(MINT_MONO_LDDOMAIN, "mono_lddomain", 2, 1, 0, MintOpNoArgs)
OPDEF(MINT_MONO_ENABLE_GCTRANS, "mono_enable_gctrans", 1, 0, 0, MintOpNoArgs)
diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c
index 6e8a5ea0ea94d..f240fb3d97e28 100644
--- a/src/mono/mono/mini/interp/transform.c
+++ b/src/mono/mono/mini/interp/transform.c
@@ -2396,9 +2396,9 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
interp_ins_set_dreg (td->last_ins, td->sp [-1].local);
td->ip += 5;
return TRUE;
- } else if (!strcmp (tm, "InternalGetHashCode")) {
+ } else if (!strcmp (tm, "GetHashCode") || !strcmp (tm, "InternalGetHashCode")) {
*op = MINT_INTRINS_GET_HASHCODE;
- } else if (!strcmp (tm, "InternalTryGetHashCode")) {
+ } else if (!strcmp (tm, "TryGetHashCode") || !strcmp (tm, "InternalTryGetHashCode")) {
*op = MINT_INTRINS_TRY_GET_HASHCODE;
} else if (!strcmp (tm, "GetRawData")) {
interp_add_ins (td, MINT_LDFLDA_UNSAFE);
@@ -2555,6 +2555,14 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
*op = MINT_MONO_MEMORY_BARRIER;
else if (!strcmp (tm, "Exchange") && csignature->param_count == 2 && csignature->params [0]->type == MONO_TYPE_I8 && csignature->params [1]->type == MONO_TYPE_I8)
*op = MINT_MONO_EXCHANGE_I8;
+ else if (!strcmp (tm, "CompareExchange") && csignature->param_count == 3 &&
+ (csignature->params[1]->type == MONO_TYPE_I4 ||
+ csignature->params[1]->type == MONO_TYPE_I8)) {
+ if (csignature->params[1]->type == MONO_TYPE_I4)
+ *op = MINT_MONO_CMPXCHG_I4;
+ else
+ *op = MINT_MONO_CMPXCHG_I8;
+ }
} else if (in_corlib && !strcmp (klass_name_space, "System.Threading") && !strcmp (klass_name, "Thread")) {
if (!strcmp (tm, "MemoryBarrier") && csignature->param_count == 0)
*op = MINT_MONO_MEMORY_BARRIER;
@@ -3486,7 +3494,8 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
}
}
- target_method = interp_transform_internal_calls (method, target_method, csignature, is_virtual);
+ if (op == -1)
+ target_method = interp_transform_internal_calls (method, target_method, csignature, is_virtual);
if (csignature->call_convention == MONO_CALL_VARARG)
csignature = mono_method_get_signature_checked (target_method, image, token, generic_context, error);
diff --git a/src/mono/wasm/runtime/jiterpreter-trace-generator.ts b/src/mono/wasm/runtime/jiterpreter-trace-generator.ts
index 35959b91942e9..e93610bc4bb05 100644
--- a/src/mono/wasm/runtime/jiterpreter-trace-generator.ts
+++ b/src/mono/wasm/runtime/jiterpreter-trace-generator.ts
@@ -770,6 +770,25 @@ export function generate_wasm_body (
append_stloc_tail(builder, getArgU16(ip, 1), isI32 ? WasmOpcode.i32_store : WasmOpcode.i64_store);
break;
}
+ case MintOpcode.MINT_MONO_CMPXCHG_I4:
+ builder.local("pLocals");
+ append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load); // dest
+ append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load); // newVal
+ append_ldloc(builder, getArgU16(ip, 4), WasmOpcode.i32_load); // expected
+ builder.callImport("cmpxchg_i32");
+ append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
+ break;
+ case MintOpcode.MINT_MONO_CMPXCHG_I8:
+ // because i64 values can't pass through JS cleanly (c.f getRawCwrap and
+ // EMSCRIPTEN_KEEPALIVE), we pass addresses of newVal, expected and the return value
+ // to the helper function. The "dest" for the compare-exchange is already a
+ // pointer, so load it normally
+ append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load); // dest
+ append_ldloca(builder, getArgU16(ip, 3), 0); // newVal
+ append_ldloca(builder, getArgU16(ip, 4), 0); // expected
+ append_ldloca(builder, getArgU16(ip, 1), 8, true); // oldVal
+ builder.callImport("cmpxchg_i64");
+ break;
default:
if (
diff --git a/src/mono/wasm/runtime/jiterpreter.ts b/src/mono/wasm/runtime/jiterpreter.ts
index 0455fcb4cc930..7ee521eec603f 100644
--- a/src/mono/wasm/runtime/jiterpreter.ts
+++ b/src/mono/wasm/runtime/jiterpreter.ts
@@ -260,6 +260,8 @@ function getTraceImports () {
["hasflag", "hasflag", getRawCwrap("mono_jiterp_enum_hasflag")],
["array_rank", "array_rank", getRawCwrap("mono_jiterp_get_array_rank")],
["stfld_o", "stfld_o", getRawCwrap("mono_jiterp_set_object_field")],
+ importDef("cmpxchg_i32", getRawCwrap("mono_jiterp_cas_i32")),
+ importDef("cmpxchg_i64", getRawCwrap("mono_jiterp_cas_i64")),
];
if (instrumentedMethodNames.length > 0) {
@@ -523,6 +525,21 @@ function initialize_builder (builder: WasmBuilder) {
"traceIp": WasmValtype.i32,
}, WasmValtype.void, true
);
+ builder.defineType(
+ "cmpxchg_i32", {
+ "dest": WasmValtype.i32,
+ "newVal": WasmValtype.i32,
+ "expected": WasmValtype.i32,
+ }, WasmValtype.i32, true
+ );
+ builder.defineType(
+ "cmpxchg_i64", {
+ "dest": WasmValtype.i32,
+ "newVal": WasmValtype.i32,
+ "expected": WasmValtype.i32,
+ "oldVal": WasmValtype.i32,
+ }, WasmValtype.void, true
+ );
}
function assert_not_null (