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 (