diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 94f0d2f1c85ab3..a8ea3696c15c7f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; @@ -19,7 +20,7 @@ public abstract partial class Delegate : ICloneable, ISerializable private const nint UnmanagedMarker = -1; // This is set under 3 circumstances - // 1. Multicast delegates - object[] + // 1. Multicast delegates - Wrapper[] // 2. Method cache - MethodInfo // 3. Collectible delegates - LoaderAllocator and such private object? _helperObject; @@ -45,11 +46,11 @@ public abstract partial class Delegate : ICloneable, ISerializable private bool IsClosed => _methodPtrAux == 0; - public partial bool HasSingleTarget => _helperObject is null || _helperObject.GetType() != typeof(object[]); + public partial bool HasSingleTarget => _helperObject is null || _helperObject.GetType() != typeof(Wrapper[]); public object? Target => - TryGetInvocations(out ReadOnlySpan invocations) - ? ((Delegate)invocations[^1]).Target + TryGetInvocations(out ReadOnlySpan invocations) + ? invocations[^1].Value!.Target : IsClosed ? _target : null; private unsafe MethodDesc* MethodDesc @@ -112,7 +113,7 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al // This method returns the Invocation list of this multicast delegate. public Delegate[] GetInvocationList() { - if (!TryGetInvocations(out ReadOnlySpan invocations)) + if (!TryGetInvocations(out ReadOnlySpan invocations)) { return [this]; } @@ -120,13 +121,13 @@ public Delegate[] GetInvocationList() Delegate[] invocationList = new Delegate[invocations.Length]; for (int i = 0; i < invocations.Length; i++) { - invocationList[i] = (Delegate)invocations[i]; + invocationList[i] = invocations[i].Value!; } return invocationList; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryGetInvocations(out ReadOnlySpan invocations) + private bool TryGetInvocations(out ReadOnlySpan invocations) { if (HasSingleTarget) { @@ -134,24 +135,24 @@ private bool TryGetInvocations(out ReadOnlySpan invocations) return false; } - Debug.Assert(_helperObject is object[]); - object[] invocationList = (object[])_helperObject; + Debug.Assert(_helperObject is Wrapper[]); + Wrapper[] invocationList = (Wrapper[])_helperObject; Debug.Assert(invocationList.Length > 1); Debug.Assert((uint)invocationList.Length >= (nuint)_extraData); - Debug.Assert(invocationList[0] is MulticastDelegate); + Debug.Assert(invocationList[0].Value is not null); - invocations = new ReadOnlySpan(invocationList, 0, (int)_extraData); + invocations = new ReadOnlySpan(invocationList, 0, (int)_extraData); return true; } // Used by delegate invocation list enumerator private Delegate? TryGetAt(int index) { - if (TryGetInvocations(out ReadOnlySpan invocations)) + if (TryGetInvocations(out ReadOnlySpan invocations)) { if ((uint)index < (uint)invocations.Length) - return (Delegate)invocations[index]; + return invocations[index].Value; } else if (index == 0) { @@ -205,26 +206,12 @@ public sealed override unsafe bool Equals([NotNullWhen(true)] object? obj) return false; // multicast - if (TryGetInvocations(out ReadOnlySpan invocations)) - { - if (!other.TryGetInvocations(out ReadOnlySpan otherInvocations) || invocations.Length != otherInvocations.Length) - return false; - - for (int i = 0; i < invocations.Length; i++) - { - if (!invocations[i].Equals(otherInvocations[i])) - return false; - } - - return true; - } + if (TryGetInvocations(out ReadOnlySpan invocations)) + return other.TryGetInvocations(out ReadOnlySpan otherInvocations) && invocations.SequenceEqual(otherInvocations); // unmanaged if (IsUnmanagedFunctionPtr) - { - return other.IsUnmanagedFunctionPtr && - _methodPtrAux == other._methodPtrAux; - } + return other.IsUnmanagedFunctionPtr && _methodPtrAux == other._methodPtrAux; // Under cached interface dispatch we might see the shared CID_VirtualOpenDelegateDispatch stub. // Fallback to desc comparison in such case for correctness. @@ -242,12 +229,12 @@ public sealed override unsafe bool Equals([NotNullWhen(true)] object? obj) public sealed override unsafe int GetHashCode() { - if (TryGetInvocations(out ReadOnlySpan invocations)) + if (TryGetInvocations(out ReadOnlySpan invocations)) { int hash = 0; - foreach (MulticastDelegate multicastDelegate in invocations) + foreach (ref readonly Wrapper wrapper in invocations) { - hash = hash * 33 + multicastDelegate.GetHashCode(); + hash = hash * 33 + wrapper.GetHashCode(); } return hash; } @@ -271,8 +258,8 @@ public sealed override unsafe int GetHashCode() protected virtual MethodInfo GetMethodImpl() { - return TryGetInvocations(out ReadOnlySpan invocations) - ? ((Delegate)invocations[^1]).Method + return TryGetInvocations(out ReadOnlySpan invocations) + ? invocations[^1].Value!.Method : _helperObject as MethodInfo ?? GetMethodImplUncached(); } @@ -535,28 +522,25 @@ private static Delegate InternalAlloc(RuntimeType type) return Unsafe.As(RuntimeTypeHandle.InternalAlloc(type)); } - internal static unsafe Delegate InternalAlloc(MethodTable* type) + private static unsafe Delegate InternalAlloc(MethodTable* type) { Debug.Assert(RuntimeTypeHandle.GetRuntimeType(type).IsAssignableTo(typeof(Delegate))); return Unsafe.As(RuntimeTypeHandle.InternalAllocNoChecks(type)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe bool InternalEqualTypes(object a, object b) + private static unsafe bool InternalEqualTypes(object a, object b) { if (a.GetType() == b.GetType()) return true; + #if FEATURE_TYPEEQUIVALENCE MethodTable* pMTa = RuntimeHelpers.GetMethodTable(a); MethodTable* pMTb = RuntimeHelpers.GetMethodTable(b); - bool ret; - - // only use QCall to check the type equivalence scenario - if (pMTa->HasTypeEquivalence && pMTb->HasTypeEquivalence) - ret = RuntimeHelpers.AreTypesEquivalent(pMTa, pMTb); - else - ret = false; + bool ret = pMTa->HasTypeEquivalence && pMTb->HasTypeEquivalence && + // only use QCall to check the type equivalence scenario + RuntimeHelpers.AreTypesEquivalent(pMTa, pMTb); GC.KeepAlive(a); GC.KeepAlive(b); @@ -591,7 +575,7 @@ private void DelegateConstruct(object target, IntPtr method) [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_GetMulticastInvokeSlow")] private static unsafe partial void* GetMulticastInvokeSlow(MethodTable* pMT); - internal unsafe IntPtr GetMulticastInvoke() + private unsafe IntPtr GetMulticastInvoke() { MethodTable* pMT = RuntimeHelpers.GetMethodTable(this); void* ptr = GetMulticastInvoke(pMT); @@ -608,7 +592,7 @@ internal unsafe IntPtr GetMulticastInvoke() [MethodImpl(MethodImplOptions.InternalCall)] private static extern unsafe void* GetInvokeMethod(MethodTable* pMT); - internal unsafe IntPtr GetInvokeMethod() + private unsafe IntPtr GetInvokeMethod() { MethodTable* pMT = RuntimeHelpers.GetMethodTable(this); void* ptr = GetInvokeMethod(pMT); @@ -616,7 +600,7 @@ internal unsafe IntPtr GetInvokeMethod() return (IntPtr)ptr; } - internal static unsafe IRuntimeMethodInfo CreateMethodInfo(MethodDesc* methodDesc) + private static unsafe IRuntimeMethodInfo CreateMethodInfo(MethodDesc* methodDesc) { IRuntimeMethodInfo? methodInfo = null; CreateMethodInfo(methodDesc, ObjectHandleOnStack.Create(ref methodInfo)); @@ -636,29 +620,16 @@ internal static unsafe IRuntimeMethodInfo CreateMethodInfo(MethodDesc* methodDes [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_GetMethodDesc")] private static unsafe partial MethodDesc* GetMethodDesc(ObjectHandleOnStack instance); - private static bool TrySetSlot(object?[] a, int index, object o) + internal struct Wrapper(Delegate? value) : IEquatable { - if (a[index] == null && Interlocked.CompareExchange(ref a[index], o, null) == null) - { - return true; - } + internal Delegate? Value = value; - // The slot may be already set because we have added and removed the same method before. - // Optimize this case, because it's cheaper than copying the array. - object? previous = a[index]; - if (previous is null) - { - return false; - } - - MulticastDelegate d = (MulticastDelegate)o; - MulticastDelegate dd = (MulticastDelegate)previous; - return dd._methodPtr == d._methodPtr && - dd._methodPtrAux == d._methodPtrAux && - dd._target == d._target; + public readonly bool Equals(Wrapper other) => Value!.Equals(other.Value); + public override readonly bool Equals(object? obj) => obj is Wrapper other && Equals(other); + public override readonly int GetHashCode() => Value!.GetHashCode(); } - private unsafe Delegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready = false) + private unsafe Delegate NewMulticastDelegate(Wrapper[] invocationList, int invocationCount, bool thisIsMultiCastAlready = false) { // First, allocate a new multicast delegate just like this one, i.e. same type as the this object Delegate result = InternalAlloc(RuntimeHelpers.GetMethodTable(this)); @@ -682,6 +653,23 @@ private unsafe Delegate NewMulticastDelegate(object[] invocationList, int invoca return result; } + private static bool TrySetSlot(ref Delegate? d, Delegate o) + { + Delegate? previous = d; + if (previous is null) + { + previous = Interlocked.CompareExchange(ref d, o, null); + if (previous == null) + return true; + } + + // The slot may be already set because we have added and removed the same method before. + // Optimize this case, because it's cheaper than copying the array. + return previous._methodPtr == o._methodPtr && + previous._methodPtrAux == o._methodPtrAux && + previous._target == o._target; + } + // This method will combine this delegate with the passed delegate // to form a new delegate. protected Delegate CombineImpl(Delegate? d) @@ -693,111 +681,56 @@ protected Delegate CombineImpl(Delegate? d) if (!InternalEqualTypes(this, d)) throw new ArgumentException(SR.Arg_DlgtTypeMis); - MulticastDelegate dFollow = (MulticastDelegate)d; - object[]? resultList; - int followCount = 1; - object[]? followList = dFollow._helperObject as object[]; - if (followList != null) - followCount = (int)dFollow._extraData; + Wrapper wrapper = new Wrapper(d); + ReadOnlySpan followList = d.TryGetInvocations(out ReadOnlySpan span) ? span : new ReadOnlySpan(ref wrapper); - int resultCount; - if (_helperObject is not object[] invocationList) + if (!TryGetInvocations(out ReadOnlySpan invocationList)) { - resultCount = 1 + followCount; - resultList = new object[resultCount]; - resultList[0] = this; - if (followList == null) - { - resultList[1] = dFollow; - } - else - { - for (int i = 0; i < followCount; i++) - resultList[1 + i] = followList[i]; - } - return NewMulticastDelegate(resultList, resultCount); + int newResultCount = 1 + followList.Length; + Wrapper[] newResultList = new Wrapper[newResultCount]; + newResultList[0] = new Wrapper(this); + followList.CopyTo(new Span(newResultList, 1, followList.Length)); + return NewMulticastDelegate(newResultList, newResultCount); } - int invocationCount = (int)_extraData; - resultCount = invocationCount + followCount; - resultList = null; - if (resultCount <= invocationList.Length) + int resultCount = invocationList.Length + followList.Length; + Wrapper[]? resultList = (Wrapper[])_helperObject!; + if (resultList.Length < resultCount) { - resultList = invocationList; - if (followList == null) - { - if (!TrySetSlot(resultList, invocationCount, dFollow)) - resultList = null; - } - else + resultList = null; + } + else + { + Span newInvocations = resultList.AsSpan(invocationList.Length, followList.Length); + for (int i = 0; i < followList.Length; i++) { - for (int i = 0; i < followCount; i++) - { - if (TrySetSlot(resultList, invocationCount + i, followList[i])) - { - continue; - } + if (TrySetSlot(ref newInvocations[i].Value, followList[i].Value!)) + continue; - resultList = null; - break; - } + resultList = null; + break; } } if (resultList == null) { - int allocCount = invocationList.Length; - while (allocCount < resultCount) - allocCount *= 2; - - resultList = new object[allocCount]; - - for (int i = 0; i < invocationCount; i++) - resultList[i] = invocationList[i]; - - if (followList == null) - { - resultList[invocationCount] = dFollow; - } - else - { - for (int i = 0; i < followCount; i++) - resultList[invocationCount + i] = followList[i]; - } + resultList = new Wrapper[BitOperations.RoundUpToPowerOf2((uint)resultCount)]; + invocationList.CopyTo(resultList); + followList.CopyTo(resultList.AsSpan(invocationList.Length)); } return NewMulticastDelegate(resultList, resultCount, true); } - private object[] DeleteFromInvocationList(object[] invocationList, int invocationCount, int deleteIndex, int deleteCount) + private static Wrapper[] DeleteFromInvocationList(ReadOnlySpan invocationList, int deleteIndex, int deleteCount) { - Debug.Assert(_helperObject is object[]); - object[] thisInvocationList = (object[])_helperObject; - - int allocCount = thisInvocationList.Length; - while (allocCount / 2 >= invocationCount - deleteCount) - allocCount /= 2; - - object[] newInvocationList = new object[allocCount]; + Wrapper[] newInvocationList = new Wrapper[BitOperations.RoundUpToPowerOf2((uint)(invocationList.Length - deleteCount))]; - for (int i = 0; i < deleteIndex; i++) - newInvocationList[i] = invocationList[i]; - - for (int i = deleteIndex + deleteCount; i < invocationCount; i++) - newInvocationList[i - deleteCount] = invocationList[i]; + invocationList.Slice(0, deleteIndex).CopyTo(newInvocationList); + invocationList.Slice(deleteIndex + deleteCount).CopyTo(newInvocationList.AsSpan(deleteIndex)); return newInvocationList; } - private static bool EqualInvocationLists(object[] a, object[] b, int start, int count) - { - for (int i = 0; i < count; i++) - { - if (!a[start + i].Equals(b[i])) - return false; - } - return true; - } - // This method currently looks backward on the invocation list // for an element that has Delegate based equality with value. (Doesn't // look at the invocation list.) If this is found we remove it from @@ -807,78 +740,50 @@ private static bool EqualInvocationLists(object[] a, object[] b, int start, int { // There is a special case were we are removing using a delegate as // the value we need to check for this case - // - MulticastDelegate? v = (MulticastDelegate?)d; - if (v == null) + if (d is null) return this; - if (v.HasSingleTarget) + bool isMulticast = TryGetInvocations(out ReadOnlySpan invocationList); + + if (!d.TryGetInvocations(out ReadOnlySpan otherInvocations)) { - if (_helperObject is not object[] invocationList) - { - // they are both not real Multicast - if (Equals(v)) - return null; - } - else - { - int invocationCount = (int)_extraData; - for (int i = invocationCount; --i >= 0;) - { - if (!v.Equals(invocationList[i])) - { - continue; - } + // they are both not real Multicast + if (!isMulticast) + return Equals(d) ? null : this; - if (invocationCount == 2) - { - // Special case - only one value left, either at the beginning or the end - return (Delegate)invocationList[1 - i]; - } + int index = invocationList.LastIndexOf(new Wrapper(d)); + if (index < 0) + return this; - object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1); - return NewMulticastDelegate(list, invocationCount - 1, true); - } - } - } - else if (_helperObject is object[] invocationList) - { - int invocationCount = (int)_extraData; - int vInvocationCount = (int)v._extraData; - object[] vInvocationList = (object[])v._helperObject!; - for (int i = invocationCount - vInvocationCount; i >= 0; i--) - { - if (!EqualInvocationLists(invocationList, vInvocationList, i, vInvocationCount)) - { - continue; - } + // Special case - only one value left, either at the beginning or the end + if (invocationList.Length == 2) + return invocationList[1 - index].Value; - switch (invocationCount - vInvocationCount) - { - case 0: - // Special case - no values left - return null; - case 1: - // Special case - only one value left, either at the beginning or the end - return (Delegate)invocationList[i != 0 ? 0 : invocationCount - 1]; - default: - { - object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, - vInvocationCount); - return NewMulticastDelegate(list, invocationCount - vInvocationCount, true); - } - } - } + Wrapper[] list = DeleteFromInvocationList(invocationList, index, 1); + return NewMulticastDelegate(list, invocationList.Length - 1, true); } - return this; - } + if (!isMulticast) + return this; - // this should help inlining - [DoesNotReturn] - [DebuggerNonUserCode] - private static void ThrowNullThisInDelegateToInstance() => - throw new ArgumentException(SR.Arg_DlgtNullInst); + int i = invocationList.LastIndexOf(otherInvocations); + if (i < 0) + return this; + + int newCount = invocationList.Length - otherInvocations.Length; + switch (newCount) + { + case 0: + // Special case - no values left + return null; + case 1: + // Special case - only one value left, either at the beginning or the end + return invocationList[i == 0 ? ^1 : 0].Value; + default: + Wrapper[] list = DeleteFromInvocationList(invocationList, i, otherInvocations.Length); + return NewMulticastDelegate(list, newCount, true); + } + } internal static IntPtr AdjustTarget(object target, IntPtr methodPtr) { @@ -897,6 +802,11 @@ internal void InitializeVirtualCallStub(IntPtr methodPtr) [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_InitializeVirtualCallStub")] private static partial void InitializeVirtualCallStub(ObjectHandleOnStack d, IntPtr methodPtr); + [DoesNotReturn] + [DebuggerNonUserCode] + private static void ThrowNullThisInDelegateToInstance() => + throw new ArgumentException(SR.Arg_DlgtNullInst); + #pragma warning disable IDE0060 [DebuggerNonUserCode] [DebuggerStepThrough] diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 5b6ee5b8017e6a..1c596f4e7d2de2 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -2076,7 +2076,8 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat pCode->EmitLoadThis(); pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__HELPER_OBJECT))); pCode->EmitLDLOC(dwLoopCounterNum); - pCode->EmitLDELEM_REF(); + pCode->EmitLDELEMA(pCode->GetToken(CoreLibBinder::GetClass(CLASS__DELEGATEWRAPPER))); + pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATEWRAPPER__VALUE))); // Load the arguments for (UINT paramCount = 0; paramCount < sig.NumFixedArgs(); paramCount++) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 0881a025eb81e3..1584e25df9f8f1 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -250,6 +250,9 @@ DEFINE_METHOD(DELEGATE, CTOR_COLLECTIBLE_CLOSED_STATIC, CtorCollectibl DEFINE_METHOD(DELEGATE, CTOR_COLLECTIBLE_OPEN, CtorCollectibleOpen, NoSig) DEFINE_METHOD(DELEGATE, CTOR_COLLECTIBLE_VIRTUAL_DISPATCH, CtorCollectibleVirtualDispatch, NoSig) +DEFINE_CLASS(DELEGATEWRAPPER, System, Delegate+Wrapper) +DEFINE_FIELD(DELEGATEWRAPPER, VALUE, Value) + DEFINE_CLASS(INT128, System, Int128) DEFINE_CLASS(UINT128, System, UInt128)