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 94ed1f03b71ae7..6e3fc24efbfce1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -7,29 +7,74 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using System.Runtime.Versioning; +using System.Threading; namespace System { [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] + [NonVersionable] public abstract partial class Delegate : ICloneable, ISerializable { - // MethodBase, either cached after first request or assigned from a DynamicMethod - // For open delegates to collectible types, this may be a LoaderAllocator object - internal object? _methodBase; + private const nint UnmanagedMarker = -1; + + // This is set under 3 circumstances + // 1. Multicast delegate + // 2. Method cache + // 3. Collectible delegates + internal object? _invocationList; // Initialized by VM as needed // _target is the object we will invoke on; null if static delegate - internal object? _target; // Keep _target and _methodPtr next to each other for optimal delegate invoke performance + // Keep _target and _methodPtr next to each other for optimal delegate invoke performance + internal object? _target; // _methodPtr is a pointer to the method we will invoke // It could be a small thunk if this is a static or UM call - internal IntPtr _methodPtr; + internal nuint _methodPtr; // In the case of a static method passed to a delegate, this field stores // whatever _methodPtr would have stored: and _methodPtr points to a // small thunk which removes the "this" pointer before going on // to _methodPtrAux. - internal IntPtr _methodPtrAux; + internal nint _methodPtrAux; + + // this stores the multicast count, UnmanagedMarker for unmanaged or target MethodDesc + internal nint _invocationCount; + + internal bool IsUnmanagedFunctionPtr => _invocationCount == UnmanagedMarker; + + public partial bool HasSingleTarget => _invocationList is null || _invocationList.GetType() != typeof(object[]); + + private nuint MethodDesc + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(HasSingleTarget); + Debug.Assert(!IsUnmanagedFunctionPtr); + return _invocationCount != 0 ? (nuint)_invocationCount : GetMethodDesc(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetInvocations(out ReadOnlySpan invocations) + { + if (HasSingleTarget) + { + invocations = default; + return false; + } + + object[] invocationList = (object[])_invocationList!; + Debug.Assert(invocationList.Length > 1); + Debug.Assert(invocationList[0] is MulticastDelegate); + Debug.Assert((uint)invocationList.Length >= (nuint)_invocationCount); + + ref MulticastDelegate first = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(invocationList)); + invocations = MemoryMarshal.CreateReadOnlySpan(ref first, (int)_invocationCount); + return true; + } // This constructor is called from the class generated by the // compiler generated code @@ -80,20 +125,42 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al protected virtual object? DynamicInvokeImpl(object?[]? args) { RuntimeMethodHandleInternal method = new RuntimeMethodHandleInternal(GetInvokeMethod()); - RuntimeMethodInfo invoke = (RuntimeMethodInfo)RuntimeType.GetMethodBase((RuntimeType)this.GetType(), method)!; + RuntimeMethodInfo invoke = (RuntimeMethodInfo)RuntimeType.GetMethodBase((RuntimeType)GetType(), method)!; return invoke.Invoke(this, BindingFlags.Default, null, args, null); } + // equals returns true IIF the delegate is not null and has the + // same target, method and invocation list as this object public override bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || !InternalEqualTypes(this, obj)) + if (obj == null) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (!InternalEqualTypes(this, obj)) return false; - Delegate d = (Delegate)obj; + // Since this is a Delegate and we know the types are the same, obj should also be a Delegate + Debug.Assert(obj is Delegate, "Shouldn't have failed here since we already checked the types are the same!"); + Delegate other = Unsafe.As(obj); + + if (TryGetInvocations(out ReadOnlySpan invocations)) + { + return other.TryGetInvocations(out ReadOnlySpan otherInvocations) && invocations.SequenceEqual(otherInvocations); + } + + if (IsUnmanagedFunctionPtr) + { + return other.IsUnmanagedFunctionPtr && + _methodPtr == other._methodPtr && + _methodPtrAux == other._methodPtrAux; + } // do an optimistic check first. This is hopefully cheap enough to be worth - if (_target == d._target && _methodPtr == d._methodPtr && _methodPtrAux == d._methodPtrAux) + if (_target == other._target && + _methodPtr == other._methodPtr && + _methodPtrAux == other._methodPtrAux) return true; // even though the fields were not all equals the delegates may still match @@ -102,77 +169,108 @@ public override bool Equals([NotNullWhen(true)] object? obj) // It may also happen that the method pointer was not jitted when creating one delegate and jitted in the other // if that's the case the delegates may still be equals but we need to make a more complicated check - if (_methodPtrAux == IntPtr.Zero) + if (_methodPtrAux == 0) { - if (d._methodPtrAux != IntPtr.Zero) + if (other._methodPtrAux != 0) return false; // different delegate kind // they are both closed over the first arg - if (_target != d._target) + if (_target != other._target) return false; // fall through method handle check } else { - if (d._methodPtrAux == IntPtr.Zero) + if (other._methodPtrAux == 0) return false; // different delegate kind // Ignore the target as it will be the delegate instance, though it may be a different one - /* - if (_methodPtr != d._methodPtr) - return false; - */ - if (_methodPtrAux == d._methodPtrAux) + if (_methodPtrAux == other._methodPtrAux) return true; // fall through method handle check } - // method ptrs don't match, go down long path - - if (_methodBase is MethodInfo && d._methodBase is MethodInfo) - return _methodBase.Equals(d._methodBase); - else - return InternalEqualMethodHandles(this, d); + return MethodDesc == other.MethodDesc; } public override int GetHashCode() { - // - // this is not right in the face of a method being jitted in one delegate and not in another - // in that case the delegate is the same and Equals will return true but GetHashCode returns a - // different hashcode which is not true. - /* - if (_methodPtrAux == IntPtr.Zero) - return unchecked((int)((long)this._methodPtr)); - else - return unchecked((int)((long)this._methodPtrAux)); - */ - if (_methodPtrAux == IntPtr.Zero) - return (_target != null ? RuntimeHelpers.GetHashCode(_target) * 33 : 0) + GetType().GetHashCode(); - else - return GetType().GetHashCode(); + if (TryGetInvocations(out ReadOnlySpan invocations)) + { + int hash = 0; + foreach (MulticastDelegate multicastDelegate in invocations) + { + hash = hash * 33 + multicastDelegate.GetHashCode(); + } + return hash; + } + + if (IsUnmanagedFunctionPtr) + { + return HashCode.Combine(_methodPtr, _methodPtr); + } + + int hashCode = MethodDesc.GetHashCode(); + if (_methodPtrAux == 0 && _target != null) + { + hashCode += RuntimeHelpers.GetHashCode(_target) * 33; + } + return hashCode; + } + + private object? GetTarget() + { + return TryGetInvocations(out ReadOnlySpan invocations) + ? invocations[^1].Target + : !IsUnmanagedFunctionPtr && _methodPtrAux == 0 ? _target : null; } protected virtual MethodInfo GetMethodImpl() { - if (_methodBase is MethodInfo methodInfo) + return TryGetInvocations(out ReadOnlySpan invocations) + ? invocations[^1].Method + : _invocationList as MethodInfo ?? GetMethodImplUncached(); + + [MethodImpl(MethodImplOptions.NoInlining)] + MethodInfo GetMethodImplUncached() { - return methodInfo; + Debug.Assert(HasSingleTarget); + + MethodInfo method = GetMethodImplCore(); + Debug.Assert(method is not null); + + Volatile.Write(ref _invocationList, method); + return method; } + } + + private MethodInfo GetMethodImplCore() + { + // should be handled by GetMethodImpl + Debug.Assert(HasSingleTarget); - IRuntimeMethodInfo method = FindMethodHandle(); + IRuntimeMethodInfo method = CreateMethodInfo(IsUnmanagedFunctionPtr ? GetMethodDesc() : MethodDesc); RuntimeType? declaringType = RuntimeMethodHandle.GetDeclaringType(method); // need a proper declaring type instance method on a generic type if (declaringType.IsGenericType) { - bool isStatic = (RuntimeMethodHandle.GetAttributes(method) & MethodAttributes.Static) != (MethodAttributes)0; - if (!isStatic) + bool isStatic = (RuntimeMethodHandle.GetAttributes(method) & MethodAttributes.Static) != 0; + if (IsUnmanagedFunctionPtr) { - if (_methodPtrAux == IntPtr.Zero) + // we handle unmanaged function pointers here because the generic ones (used for WinRT) would otherwise + // be treated as open delegates, resulting in failure to get the MethodInfo + + // we are returning the 'Invoke' method of this delegate so use this.GetType() for the exact type + RuntimeType reflectedType = (RuntimeType)GetType(); + declaringType = reflectedType; + } + else if (!isStatic) + { + if (_methodPtrAux == 0) { // The target may be of a derived type that doesn't have visibility onto the // target method. We don't want to call RuntimeType.GetMethodBase below with that @@ -208,17 +306,20 @@ protected virtual MethodInfo GetMethodImpl() else { // it's an open one, need to fetch the first arg of the instantiation - MethodInfo invoke = this.GetType().GetMethod("Invoke")!; + MethodInfo invoke = GetType().GetMethod("Invoke")!; declaringType = (RuntimeType)invoke.GetParametersAsSpan()[0].ParameterType; } } } - - _methodBase = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method)!; - return (MethodInfo)_methodBase; + return (MethodInfo)RuntimeType.GetMethodBase(declaringType, method)!; } - public object? Target => GetTarget(); + internal void StoreDynamicMethod(MethodInfo dynamicMethod) + { + Debug.Assert(!IsUnmanagedFunctionPtr); + Debug.Assert(HasSingleTarget, "dynamic method with invocation list"); + _invocationList = dynamicMethod; + } // V1 API. [RequiresUnreferencedCode("The target method might be removed")] @@ -247,10 +348,7 @@ protected virtual MethodInfo GetMethodImpl() DelegateBindingFlags.NeverCloseOverNull | (ignoreCase ? DelegateBindingFlags.CaselessMatching : 0))) { - if (throwOnBindFailure) - throw new ArgumentException(SR.Arg_DlgtTargMeth); - - return null; + return throwOnBindFailure ? throw new ArgumentException(SR.Arg_DlgtTargMeth) : null; } return d; @@ -283,10 +381,7 @@ protected virtual MethodInfo GetMethodImpl() DelegateBindingFlags.OpenDelegateOnly | (ignoreCase ? DelegateBindingFlags.CaselessMatching : 0))) { - if (throwOnBindFailure) - throw new ArgumentException(SR.Arg_DlgtTargMeth); - - return null; + return throwOnBindFailure ? throw new ArgumentException(SR.Arg_DlgtTargMeth) : null; } return d; @@ -321,10 +416,7 @@ protected virtual MethodInfo GetMethodImpl() null, DelegateBindingFlags.OpenDelegateOnly | DelegateBindingFlags.RelaxedSignature); - if (d == null && throwOnBindFailure) - throw new ArgumentException(SR.Arg_DlgtTargMeth); - - return d; + return d == null && throwOnBindFailure ? throw new ArgumentException(SR.Arg_DlgtTargMeth) : d; } // V2 API. @@ -353,10 +445,7 @@ protected virtual MethodInfo GetMethodImpl() firstArgument, DelegateBindingFlags.RelaxedSignature); - if (d == null && throwOnBindFailure) - throw new ArgumentException(SR.Arg_DlgtTargMeth); - - return d; + return d == null && throwOnBindFailure ? throw new ArgumentException(SR.Arg_DlgtTargMeth) : d; } // @@ -379,8 +468,7 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target // Initialize the method... Delegate d = InternalAlloc(rtType); - // This is a new internal API added in Whidbey. Currently it's only - // used by the dynamic method code to generate a wrapper delegate. + // This is a new internal API added in Whidbey. // Allow flexible binding options since the target method is // unambiguously provided to us. @@ -396,10 +484,7 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target { Delegate d = InternalAlloc(rtType); - if (d.BindToMethodInfo(firstArgument, rtMethod, rtMethod.GetDeclaringTypeInternal(), flags)) - return d; - else - return null; + return d.BindToMethodInfo(firstArgument, rtMethod, rtMethod.GetDeclaringTypeInternal(), flags) ? d : null; } // @@ -488,7 +573,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() + internal unsafe nuint GetMulticastInvoke() { MethodTable* pMT = RuntimeHelpers.GetMethodTable(this); void* ptr = GetMulticastInvoke(pMT); @@ -499,7 +584,7 @@ internal unsafe IntPtr GetMulticastInvoke() Debug.Assert(ptr == GetMulticastInvoke(pMT)); } // No GC.KeepAlive() since the caller must keep instance alive to use returned pointer. - return (IntPtr)ptr; + return (nuint)ptr; } [MethodImpl(MethodImplOptions.InternalCall)] @@ -513,25 +598,30 @@ internal unsafe IntPtr GetInvokeMethod() return (IntPtr)ptr; } - internal IRuntimeMethodInfo FindMethodHandle() + internal static IRuntimeMethodInfo CreateMethodInfo(nuint methodDesc) { - Delegate d = this; IRuntimeMethodInfo? methodInfo = null; - FindMethodHandle(ObjectHandleOnStack.Create(ref d), ObjectHandleOnStack.Create(ref methodInfo)); + CreateMethodInfo(methodDesc, ObjectHandleOnStack.Create(ref methodInfo)); return methodInfo!; } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_FindMethodHandle")] - private static partial void FindMethodHandle(ObjectHandleOnStack d, ObjectHandleOnStack retMethodInfo); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_CreateMethodInfo")] + private static partial void CreateMethodInfo(nuint methodDesc, ObjectHandleOnStack retMethodInfo); - private static bool InternalEqualMethodHandles(Delegate left, Delegate right) + [MethodImpl(MethodImplOptions.NoInlining)] + private nuint GetMethodDesc() { - return InternalEqualMethodHandles(ObjectHandleOnStack.Create(ref left), ObjectHandleOnStack.Create(ref right)); + Delegate d = this; + nuint desc = GetMethodDesc(ObjectHandleOnStack.Create(ref d)); + if (!IsUnmanagedFunctionPtr) + { + _invocationCount = (nint)desc; + } + return desc; } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_InternalEqualMethodHandles")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool InternalEqualMethodHandles(ObjectHandleOnStack left, ObjectHandleOnStack right); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_GetMethodDesc")] + private static partial nuint GetMethodDesc(ObjectHandleOnStack instance); internal static IntPtr AdjustTarget(object target, IntPtr methodPtr) { @@ -549,11 +639,6 @@ internal void InitializeVirtualCallStub(IntPtr methodPtr) [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_InitializeVirtualCallStub")] private static partial void InitializeVirtualCallStub(ObjectHandleOnStack d, IntPtr methodPtr); - - internal virtual object? GetTarget() - { - return (_methodPtrAux == IntPtr.Zero) ? _target : null; - } } // These flags effect the way BindToMethodInfo and BindToMethodName are allowed to bind a delegate to a target method. Their diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs index 38fd1f5189f6ec..b53c77947a540f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs @@ -4,8 +4,6 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -17,23 +15,6 @@ namespace System [ComVisible(true)] public abstract partial class MulticastDelegate : Delegate { - // This is set under 3 circumstances - // 1. Multicast delegate - // 2. Unmanaged function pointer - // 3. Open virtual delegate - private object? _invocationList; // Initialized by VM as needed - private nint _invocationCount; - - internal bool IsUnmanagedFunctionPtr() - { - return _invocationCount == -1; - } - - internal bool InvocationListLogicallyNull() - { - return (_invocationList == null) || (_invocationList is LoaderAllocator) || (_invocationList is DynamicResolver); - } - [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] public override void GetObjectData(SerializationInfo info, StreamingContext context) @@ -41,98 +22,26 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont throw new SerializationException(SR.Serialization_DelegatesNotSupported); } - // equals returns true IIF the delegate is not null and has the - // same target, method and invocation list as this object - public sealed override bool Equals([NotNullWhen(true)] object? obj) - { - if (obj == null) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (!InternalEqualTypes(this, obj)) - return false; - - // Since this is a MulticastDelegate and we know - // the types are the same, obj should also be a - // MulticastDelegate - Debug.Assert(obj is MulticastDelegate, "Shouldn't have failed here since we already checked the types are the same!"); - MulticastDelegate d = Unsafe.As(obj); - - if (_invocationCount != 0) - { - // there are 3 kind of delegate kinds that fall into this bucket - // 1- Multicast (_invocationList is Object[]) - // 2- Unmanaged FntPtr (_invocationList == null) - // 3- Open virtual (_invocationCount == MethodDesc of target, _invocationList == null, LoaderAllocator, or DynamicResolver) - - if (InvocationListLogicallyNull()) - { - if (IsUnmanagedFunctionPtr()) - { - if (!d.IsUnmanagedFunctionPtr()) - return false; - - return _methodPtr == d._methodPtr - && _methodPtrAux == d._methodPtrAux; - } - - return base.Equals(obj); - } - else - { - Debug.Assert(_invocationList is object[], "empty invocation list on multicast delegate"); - return InvocationListEquals(d); - } - } - else - { - return base.Equals(d); - } - } - - // Recursive function which will check for equality of the invocation list. - private bool InvocationListEquals(MulticastDelegate d) - { - Debug.Assert(d != null); - Debug.Assert(_invocationList is object[]); - object[] invocationList = (object[])_invocationList; - - if (d._invocationCount != _invocationCount) - return false; - - int invocationCount = (int)_invocationCount; - for (int i = 0; i < invocationCount; i++) - { - Debug.Assert(invocationList[i] is Delegate); - Delegate dd = (Delegate)invocationList[i]; // If invocationList is an object[], it always contains Delegate (or MulticastDelegate) objects - - object[] dInvocationList = (d._invocationList as object[])!; - if (!dd.Equals(dInvocationList[i])) - return false; - } - return true; - } - private static bool TrySetSlot(object?[] a, int index, object o) { if (a[index] == null && Interlocked.CompareExchange(ref a[index], o, null) == 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. - if (a[index] is object ai) + object? previous = a[index]; + if (previous is null) { - MulticastDelegate d = (MulticastDelegate)o; - MulticastDelegate dd = (MulticastDelegate)ai; - - if (dd._methodPtr == d._methodPtr && - dd._target == d._target && - dd._methodPtrAux == d._methodPtrAux) - { - return true; - } + return false; } - return false; + + MulticastDelegate d = (MulticastDelegate)o; + MulticastDelegate dd = (MulticastDelegate)previous; + return dd._methodPtr == d._methodPtr && + dd._methodPtrAux == d._methodPtrAux && + dd._target == d._target; } private unsafe MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready) @@ -144,8 +53,8 @@ private unsafe MulticastDelegate NewMulticastDelegate(object[] invocationList, i // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them if (thisIsMultiCastAlready) { - result._methodPtr = this._methodPtr; - result._methodPtrAux = this._methodPtrAux; + result._methodPtr = _methodPtr; + result._methodPtrAux = _methodPtrAux; } else { @@ -164,12 +73,6 @@ internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int inv return NewMulticastDelegate(invocationList, invocationCount, false); } - internal void StoreDynamicMethod(MethodInfo dynamicMethod) - { - Debug.Assert(_invocationCount == 0); - _methodBase = dynamicMethod; - } - // This method will combine this delegate with the passed delegate // to form a new delegate. protected sealed override Delegate CombineImpl(Delegate? follow) @@ -205,61 +108,63 @@ protected sealed override Delegate CombineImpl(Delegate? follow) } return NewMulticastDelegate(resultList, resultCount); } - else + + int invocationCount = (int)_invocationCount; + resultCount = invocationCount + followCount; + resultList = null; + if (resultCount <= invocationList.Length) { - int invocationCount = (int)_invocationCount; - resultCount = invocationCount + followCount; - resultList = null; - if (resultCount <= invocationList.Length) + resultList = invocationList; + if (followList == null) { - resultList = invocationList; - if (followList == null) - { - if (!TrySetSlot(resultList, invocationCount, dFollow)) - resultList = null; - } - else + if (!TrySetSlot(resultList, invocationCount, dFollow)) + resultList = null; + } + else + { + for (int i = 0; i < followCount; i++) { - for (int i = 0; i < followCount; i++) + if (TrySetSlot(resultList, invocationCount + i, followList[i])) { - if (!TrySetSlot(resultList, invocationCount + i, followList[i])) - { - resultList = null; - break; - } + continue; } + + resultList = null; + break; } } + } - if (resultList == null) - { - int allocCount = invocationList.Length; - while (allocCount < resultCount) - allocCount *= 2; + if (resultList == null) + { + int allocCount = invocationList.Length; + while (allocCount < resultCount) + allocCount *= 2; - resultList = new object[allocCount]; + resultList = new object[allocCount]; - for (int i = 0; i < invocationCount; i++) - resultList[i] = invocationList[i]; + 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]; - } + if (followList == null) + { + resultList[invocationCount] = dFollow; + } + else + { + for (int i = 0; i < followCount; i++) + resultList[invocationCount + i] = followList[i]; } - return NewMulticastDelegate(resultList, resultCount, true); } + return NewMulticastDelegate(resultList, resultCount, true); } private object[] DeleteFromInvocationList(object[] invocationList, int invocationCount, int deleteIndex, int deleteCount) { Debug.Assert(_invocationList is object[]); - object[] thisInvocationList = (object[])_invocationList; + object[]? thisInvocationList = Unsafe.As(_invocationList); + Debug.Assert(thisInvocationList is not null); + int allocCount = thisInvocationList.Length; while (allocCount / 2 >= invocationCount - deleteCount) allocCount /= 2; @@ -290,43 +195,43 @@ private static bool EqualInvocationLists(object[] a, object[] b, int start, int // look at the invocation list.) If this is found we remove it from // this list and return a new delegate. If its not found a copy of the // current list is returned. - protected sealed override Delegate? RemoveImpl(Delegate value) + protected sealed override Delegate? RemoveImpl(Delegate? value) { // There is a special case were we are removing using a delegate as // the value we need to check for this case // - MulticastDelegate? v = value as MulticastDelegate; - + MulticastDelegate? v = (MulticastDelegate?)value; if (v == null) return this; - if (v._invocationList is not object[]) + + if (v.HasSingleTarget) { - if (_invocationList is not object[] invocationList) - { - // they are both not real Multicast - if (this.Equals(value)) - return null; - } - else + if (_invocationList is object[] invocationList) { int invocationCount = (int)_invocationCount; for (int i = invocationCount; --i >= 0;) { - if (value.Equals(invocationList[i])) + if (!value!.Equals(invocationList[i])) { - if (invocationCount == 2) - { - // Special case - only one value left, either at the beginning or the end - return (Delegate)invocationList[1 - i]; - } - else - { - object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1); - return NewMulticastDelegate(list, invocationCount - 1, true); - } + continue; + } + + if (invocationCount == 2) + { + // Special case - only one value left, either at the beginning or the end + return (Delegate)invocationList[1 - i]; } + + object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1); + return NewMulticastDelegate(list, invocationCount - 1, true); } } + else + { + // they are both not real Multicast + if (Equals(value)) + return null; + } } else { @@ -334,23 +239,26 @@ private static bool EqualInvocationLists(object[] a, object[] b, int start, int { int invocationCount = (int)_invocationCount; int vInvocationCount = (int)v._invocationCount; + object[] vInvocationList = (object[])v._invocationList!; for (int i = invocationCount - vInvocationCount; i >= 0; i--) { - if (EqualInvocationLists(invocationList, (v._invocationList as object[])!, i, vInvocationCount)) + if (!EqualInvocationLists(invocationList, vInvocationList, i, vInvocationCount)) { - if (invocationCount - vInvocationCount == 0) - { + continue; + } + + switch (invocationCount - vInvocationCount) + { + case 0: // Special case - no values left return null; - } - else if (invocationCount - vInvocationCount == 1) - { + case 1: // Special case - only one value left, either at the beginning or the end return (Delegate)invocationList[i != 0 ? 0 : invocationCount - 1]; - } - else + default: { - object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, vInvocationCount); + object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, + vInvocationCount); return NewMulticastDelegate(list, invocationCount - vInvocationCount, true); } } @@ -364,119 +272,27 @@ private static bool EqualInvocationLists(object[] a, object[] b, int start, int // This method returns the Invocation list of this multicast delegate. public sealed override Delegate[] GetInvocationList() { - Delegate[] del; - if (_invocationList is not object[] invocationList) - { - del = new Delegate[1]; - del[0] = this; - } - else - { - // Create an array of delegate copies and each - // element into the array - del = new Delegate[(int)_invocationCount]; - - for (int i = 0; i < del.Length; i++) - del[i] = (Delegate)invocationList[i]; - } - return del; + return TryGetInvocations(out ReadOnlySpan invocations) ? ((ReadOnlySpan)invocations).ToArray() : [this]; } - internal new bool HasSingleTarget => _invocationList is not object[]; - // Used by delegate invocation list enumerator - internal object? /* Delegate? */ TryGetAt(int index) - { - if (_invocationList is not object[] invocationList) - { - return (index == 0) ? this : null; - } - else - { - return ((uint)index < (uint)_invocationCount) ? invocationList[index] : null; - } - } - - public sealed override int GetHashCode() + internal Delegate? TryGetAt(int index) { - if (IsUnmanagedFunctionPtr()) - return HashCode.Combine(_methodPtr, _methodPtrAux); - - if (_invocationList is not object[] invocationList) + if (TryGetInvocations(out ReadOnlySpan invocations)) { - return base.GetHashCode(); + if ((uint)index < (uint)invocations.Length) + return invocations[index]; } - else + else if (index == 0) { - int hash = 0; - for (int i = 0; i < (int)_invocationCount; i++) - { - hash = hash * 33 + invocationList[i].GetHashCode(); - } - - return hash; + return this; } - } - internal override object? GetTarget() - { - if (_invocationCount != 0) - { - // _invocationCount != 0 we are in one of these cases: - // - Multicast -> return the target of the last delegate in the list - // - unmanaged function pointer - return null - // - virtual open delegate - return null - if (InvocationListLogicallyNull()) - { - // both open virtual and ftn pointer return null for the target - return null; - } - else - { - if (_invocationList is object[] invocationList) - { - int invocationCount = (int)_invocationCount; - return ((Delegate)invocationList[invocationCount - 1]).GetTarget(); - } - } - } - return base.GetTarget(); + return null; } - protected override MethodInfo GetMethodImpl() - { - if (_invocationList is object[] invocationList) - { - // multicast case - int index = (int)_invocationCount - 1; - return ((Delegate)invocationList[index]).Method; - } - else if (IsUnmanagedFunctionPtr()) - { - // we handle unmanaged function pointers here because the generic ones (used for WinRT) would otherwise - // be treated as open delegates by the base implementation, resulting in failure to get the MethodInfo - if (_methodBase is MethodInfo methodInfo) - { - return methodInfo; - } - - IRuntimeMethodInfo method = FindMethodHandle(); - RuntimeType declaringType = RuntimeMethodHandle.GetDeclaringType(method); - - // need a proper declaring type instance method on a generic type - if (declaringType.IsGenericType) - { - // we are returning the 'Invoke' method of this delegate so use this.GetType() for the exact type - RuntimeType reflectedType = (RuntimeType)GetType(); - declaringType = reflectedType; - } - - _methodBase = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method)!; - return (MethodInfo)_methodBase; - } - - return base.GetMethodImpl(); - } + public sealed override bool Equals([NotNullWhen(true)] object? obj) => base.Equals(obj); + public sealed override int GetHashCode() => base.GetHashCode(); // this should help inlining [DoesNotReturn] @@ -491,16 +307,16 @@ private void CtorClosed(object target, IntPtr methodPtr) { if (target == null) ThrowNullThisInDelegateToInstance(); - this._target = target; - this._methodPtr = methodPtr; + _target = target; + _methodPtr = (nuint)methodPtr; } [DebuggerNonUserCode] [DebuggerStepThrough] private void CtorClosedStatic(object target, IntPtr methodPtr) { - this._target = target; - this._methodPtr = methodPtr; + _target = target; + _methodPtr = (nuint)methodPtr; } [DebuggerNonUserCode] @@ -509,55 +325,58 @@ private void CtorRTClosed(object target, IntPtr methodPtr) { if (target == null) ThrowNullThisInDelegateToInstance(); - this._target = target; - this._methodPtr = AdjustTarget(target, methodPtr); + _target = target; + _methodPtr = (nuint)AdjustTarget(target, methodPtr); } [DebuggerNonUserCode] [DebuggerStepThrough] private void CtorOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk) { - this._target = this; - this._methodPtr = shuffleThunk; - this._methodPtrAux = methodPtr; + _target = this; + _methodPtr = (nuint)shuffleThunk; + _methodPtrAux = methodPtr; } [DebuggerNonUserCode] [DebuggerStepThrough] private void CtorVirtualDispatch(object target, IntPtr methodPtr, IntPtr shuffleThunk) { - this._target = this; - this._methodPtr = shuffleThunk; - this.InitializeVirtualCallStub(methodPtr); + _target = this; + _methodPtr = (nuint)shuffleThunk; + InitializeVirtualCallStub(methodPtr); } [DebuggerNonUserCode] [DebuggerStepThrough] private void CtorCollectibleClosedStatic(object target, IntPtr methodPtr, IntPtr gchandle) { - this._target = target; - this._methodPtr = methodPtr; - this._methodBase = GCHandle.InternalGet(gchandle); + _target = target; + _methodPtr = (nuint)methodPtr; + _invocationList = GCHandle.InternalGet(gchandle); + Debug.Assert(HasSingleTarget); } [DebuggerNonUserCode] [DebuggerStepThrough] private void CtorCollectibleOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk, IntPtr gchandle) { - this._target = this; - this._methodPtr = shuffleThunk; - this._methodPtrAux = methodPtr; - this._methodBase = GCHandle.InternalGet(gchandle); + _target = this; + _methodPtr = (nuint)shuffleThunk; + _methodPtrAux = methodPtr; + _invocationList = GCHandle.InternalGet(gchandle); + Debug.Assert(HasSingleTarget); } [DebuggerNonUserCode] [DebuggerStepThrough] private void CtorCollectibleVirtualDispatch(object target, IntPtr methodPtr, IntPtr shuffleThunk, IntPtr gchandle) { - this._target = this; - this._methodPtr = shuffleThunk; - this._methodBase = GCHandle.InternalGet(gchandle); - this.InitializeVirtualCallStub(methodPtr); + _target = this; + _methodPtr = (nuint)shuffleThunk; + _invocationList = GCHandle.InternalGet(gchandle); + Debug.Assert(HasSingleTarget); + InitializeVirtualCallStub(methodPtr); } #pragma warning restore IDE0060 } diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index adb057fba3011a..c71e0302ffe628 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3262,9 +3262,10 @@ DacDbiInterfaceImpl::DelegateType DacDbiInterfaceImpl::GetDelegateType(VMPTR_Obj DelegateType delegateType = DelegateType::kUnknownDelegateType; PTR_DelegateObject pDelObj = dac_cast(delegateObject.GetDacPtr()); - INT_PTR invocationCount = pDelObj->GetInvocationCount(); - if (invocationCount == 0) + INT_PTR invocationCount = pDelObj->GetInvocationCount(); + OBJECTREF invocationList = pDelObj->GetInvocationList(); + if (((invocationList == NULL) || !invocationList->GetMethodTable()->IsArray()) && (invocationCount != DELEGATE_MARKER_UNMANAGEDFPTR)) { // If this delegate points to a static function or this is a open virtual delegate, this should be non-null // Special case: This might fail in a VSD delegate (instance open virtual)... diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs index 9968de32d01574..6e0b34b188301b 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs @@ -602,7 +602,7 @@ private static bool EqualInvocationLists(Wrapper[] a, Wrapper[] b, int start, in // look at the invocation list.) If this is found we remove it from // this list and return a new delegate. If its not found a copy of the // current list is returned. - protected virtual Delegate? RemoveImpl(Delegate d) + protected virtual Delegate? RemoveImpl(Delegate? d) { // There is a special case were we are removing using a delegate as // the value we need to check for this case diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 8f0d4a2438aace..47e7bc7a5d7d51 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -26,9 +26,6 @@ #include "comcallablewrapper.h" #endif // FEATURE_COMINTEROP -#define DELEGATE_MARKER_UNMANAGEDFPTR -1 - - #ifndef DACCESS_COMPILE #if defined(TARGET_X86) @@ -1231,7 +1228,6 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, // runtime. PCODE pTargetCall = GetVirtualCallStub(pTargetMethod, TypeHandle(pExactMethodType)); (*pRefThis)->SetMethodPtrAux(pTargetCall); - (*pRefThis)->SetInvocationCount((INT_PTR)(void *)pTargetMethod); } else { @@ -1261,7 +1257,7 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, } else { - PCODE pTargetCode = (PCODE)NULL; + PCODE pTargetCode = NULL; // For virtual methods we can (and should) virtualize the call now (so we don't have to insert a thunk to do so at runtime). // @@ -1291,10 +1287,13 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, (*pRefThis)->SetMethodPtr(pTargetCode); } + (*pRefThis)->SetInvocationCount((INT_PTR)pTargetMethod); + LoaderAllocator *pLoaderAllocator = pTargetMethod->GetLoaderAllocator(); + _ASSERTE((*pRefThis)->GetInvocationList() == NULL); if (pLoaderAllocator->IsCollectible()) - (*pRefThis)->SetMethodBase(pLoaderAllocator->GetExposedObject()); + (*pRefThis)->SetInvocationList(pLoaderAllocator->GetExposedObject()); } // Marshals a delegate to a unmanaged callback. @@ -1376,7 +1375,7 @@ LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj) _ASSERTE(objhnd != NULL); // This target should not ever be used. We are storing it in the thunk for better diagnostics of "call on collected delegate" crashes. - PCODE pManagedTargetForDiagnostics = (pDelegate->GetMethodPtrAux() != (PCODE)NULL) ? pDelegate->GetMethodPtrAux() : pDelegate->GetMethodPtr(); + PCODE pManagedTargetForDiagnostics = pDelegate->GetMethodPtrAux() != (PCODE)NULL ? pDelegate->GetMethodPtrAux() : pDelegate->GetMethodPtr(); // MethodDesc is passed in for profiling to know the method desc of target pUMEntryThunk->LoadTimeInit( @@ -1708,8 +1707,9 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q if (!isStatic) methodArgCount++; // count 'this' + _ASSERTE(refThis->GetInvocationList() == NULL); if (pMeth->GetLoaderAllocator()->IsCollectible()) - refThis->SetMethodBase(pMeth->GetLoaderAllocator()->GetExposedObject()); + refThis->SetInvocationList(pMeth->GetLoaderAllocator()->GetExposedObject()); // Open delegates. if (invokeArgCount == methodArgCount) @@ -1726,7 +1726,6 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q { PCODE pTargetCall = GetVirtualCallStub(pMeth, TypeHandle(pMeth->GetMethodTable())); refThis->SetMethodPtrAux(pTargetCall); - refThis->SetInvocationCount((INT_PTR)(void *)pMeth); } else { @@ -1783,23 +1782,12 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q refThis->SetMethodPtr((PCODE)(void *)method); } + refThis->SetInvocationCount((INT_PTR)pMeth); + GCPROTECT_END(); END_QCALL; } -MethodDesc *COMDelegate::GetMethodDescForOpenVirtualDelegate(OBJECTREF orDelegate) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - return (MethodDesc*)((DELEGATEREF)orDelegate)->GetInvocationCount(); -} - MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) { CONTRACTL @@ -1812,107 +1800,37 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) // If you modify this logic, please update cDAC IObject.GetDelegateInfo. - MethodDesc *pMethodHandle = NULL; - DELEGATEREF thisDel = (DELEGATEREF) orDelegate; - DELEGATEREF innerDel = NULL; INT_PTR count = thisDel->GetInvocationCount(); if (count != 0) { // this is one of the following: // - multicast - _invocationList is Array && _invocationCount != 0 - // - unamanaged ftn ptr - _invocationList == NULL && _invocationCount == -1 - // - wrapper delegate - _invocationList is Delegate && _invocationCount != NULL - // - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc) - // or _invocationList points to a LoaderAllocator/DynamicResolver (inner open virtual delegate of a Wrapper Delegate) - // in the wrapper delegate case we want to unwrap and return the method desc of the inner delegate - // in the other cases we return the method desc for the invoke - innerDel = (DELEGATEREF) thisDel->GetInvocationList(); - bool fOpenVirtualDelegate = false; - - if (innerDel != NULL) - { - MethodTable *pMT = innerDel->GetMethodTable(); - if (pMT->IsDelegate()) - return GetMethodDesc(innerDel); - if (!pMT->IsArray()) - { - // must be a virtual one - fOpenVirtualDelegate = true; - } - } - else - { - if (count != DELEGATE_MARKER_UNMANAGEDFPTR) - { - // must be a virtual one - fOpenVirtualDelegate = true; - } - } + // - unmanaged ftn ptr - _invocationList == NULL && _invocationCount == -1 + // - MethodDesc already cached - if (fOpenVirtualDelegate) - pMethodHandle = GetMethodDescForOpenVirtualDelegate(thisDel); - else - pMethodHandle = FindDelegateInvokeMethod(thisDel->GetMethodTable()); - } - else - { - // Next, check for an open delegate - PCODE code = thisDel->GetMethodPtrAux(); + // we return the method desc for the invoke + OBJECTREF innerDel = thisDel->GetInvocationList(); + if ((innerDel != NULL && innerDel->GetMethodTable()->IsArray()) || count == DELEGATE_MARKER_UNMANAGEDFPTR) + return FindDelegateInvokeMethod(thisDel->GetMethodTable()); - if (code == (PCODE)NULL) - { - // Must be a normal delegate - code = thisDel->GetMethodPtr(); - } - - pMethodHandle = NonVirtualEntry2MethodDesc(code); + return (MethodDesc*)count; } - _ASSERTE(pMethodHandle); - return pMethodHandle; -} - -OBJECTREF COMDelegate::GetTargetObject(OBJECTREF obj) -{ - CONTRACTL + // Next, check for an open delegate + PCODE code = thisDel->GetMethodPtrAux(); + if (code == NULL) { - THROWS; - GC_NOTRIGGER; - MODE_COOPERATIVE; + // Must be a normal delegate + code = thisDel->GetMethodPtr(); } - CONTRACTL_END; - - OBJECTREF targetObject = NULL; - DELEGATEREF thisDel = (DELEGATEREF) obj; - OBJECTREF innerDel = NULL; - - if (thisDel->GetInvocationCount() != 0) - { - // this is one of the following: - // - multicast - // - unmanaged ftn ptr - // - wrapper delegate - // - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc) - // or _invocationList points to a LoaderAllocator/DynamicResolver (inner open virtual delegate of a Wrapper Delegate) - // in the wrapper delegate case we want to unwrap and return the object of the inner delegate - innerDel = (DELEGATEREF) thisDel->GetInvocationList(); - if (innerDel != NULL) - { - MethodTable *pMT = innerDel->GetMethodTable(); - if (pMT->IsDelegate()) - { - targetObject = GetTargetObject(innerDel); - } - } - } - - if (targetObject == NULL) - targetObject = thisDel->GetTarget(); + MethodDesc* pMethodHandle = NonVirtualEntry2MethodDesc(code); + _ASSERTE(pMethodHandle); - return targetObject; + thisDel->SetInvocationCount((INT_PTR)pMethodHandle); + return pMethodHandle; } BOOL COMDelegate::IsTrueMulticastDelegate(OBJECTREF delegate) @@ -1927,15 +1845,11 @@ BOOL COMDelegate::IsTrueMulticastDelegate(OBJECTREF delegate) BOOL isMulticast = FALSE; - size_t invocationCount = ((DELEGATEREF)delegate)->GetInvocationCount(); - if (invocationCount) + OBJECTREF invocationList = ((DELEGATEREF)delegate)->GetInvocationList(); + if (invocationList != NULL) { - OBJECTREF invocationList = ((DELEGATEREF)delegate)->GetInvocationList(); - if (invocationList != NULL) - { - MethodTable *pMT = invocationList->GetMethodTable(); - isMulticast = pMT->IsArray(); - } + MethodTable *pMT = invocationList->GetMethodTable(); + isMulticast = pMT->IsArray(); } return isMulticast; @@ -2035,7 +1949,7 @@ void COMDelegate::ThrowIfInvalidUnmanagedCallersOnlyUsage(MethodDesc* pMD) } // This method will get the MethodInfo for a delegate -extern "C" void QCALLTYPE Delegate_FindMethodHandle(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack retMethodInfo) +extern "C" void QCALLTYPE Delegate_CreateMethodInfo(MethodDesc* methodDesc, QCall::ObjectHandleOnStack retMethodInfo) { QCALL_CONTRACT; @@ -2043,26 +1957,24 @@ extern "C" void QCALLTYPE Delegate_FindMethodHandle(QCall::ObjectHandleOnStack d GCX_COOP(); - MethodDesc* pMD = COMDelegate::GetMethodDesc(d.Get()); + MethodDesc* pMD = methodDesc; pMD = MethodDesc::FindOrCreateAssociatedMethodDescForReflection(pMD, TypeHandle(pMD->GetMethodTable()), pMD->GetMethodInstantiation()); retMethodInfo.Set(pMD->AllocateStubMethodInfo()); END_QCALL; } -extern "C" BOOL QCALLTYPE Delegate_InternalEqualMethodHandles(QCall::ObjectHandleOnStack left, QCall::ObjectHandleOnStack right) +extern "C" MethodDesc* QCALLTYPE Delegate_GetMethodDesc(QCall::ObjectHandleOnStack instance) { QCALL_CONTRACT; - BOOL fRet = FALSE; + MethodDesc* fRet = nullptr; BEGIN_QCALL; GCX_COOP(); - MethodDesc* pMDLeft = COMDelegate::GetMethodDesc(left.Get()); - MethodDesc* pMDRight = COMDelegate::GetMethodDesc(right.Get()); - fRet = pMDLeft == pMDRight; + fRet = COMDelegate::GetMethodDesc(instance.Get()); END_QCALL; @@ -2147,7 +2059,7 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat // Load next delegate from array using LoopCounter as index pCode->EmitLoadThis(); - pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_LIST))); + pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__INVOCATION_LIST))); pCode->EmitLDLOC(dwLoopCounterNum); pCode->EmitLDELEM_REF(); @@ -2171,7 +2083,7 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat // compare LoopCounter with InvocationCount. If less then branch to nextDelegate pCode->EmitLDLOC(dwLoopCounterNum); pCode->EmitLoadThis(); - pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_COUNT))); + pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__INVOCATION_COUNT))); pCode->EmitBLT(nextDelegate); // load the return value. return value from the last delegate call is returned @@ -2634,8 +2546,8 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT LoaderAllocator *pTargetMethodLoaderAllocator = pTargetMethod->GetLoaderAllocator(); BOOL isCollectible = pTargetMethodLoaderAllocator->IsCollectible(); // A method that may be instantiated over a collectible type, and is static will require a delegate - // that has the _methodBase field filled in with the LoaderAllocator of the collectible assembly - // associated with the instantiation. + // that has the LoaderAllocator of the collectible assembly associated with the instantiation + // stored in the _invocationList field. BOOL fMaybeCollectibleAndStatic = FALSE; if (isStatic) @@ -2693,14 +2605,14 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT // DELEGATE KINDS TABLE // - // _target _methodPtr _methodPtrAux _invocationList _invocationCount + // _invocationList _target _methodPtr _methodPtrAux // - // 1- Instance closed 'this' ptr target method null null 0 - // 2- Instance open non-virt delegate shuffle thunk target method null 0 - // 3- Instance open virtual delegate shuffle thunk Virtual call stub null MethodDesc of target - // 4- Static closed first arg target method null null 0 - // 5- Static closed (retbuf) delegate ThisPtrRetBuf precode null null 0 - // 6- Static opened delegate shuffle thunk target method null 0 + // 1- Instance closed null 'this' ptr target method null + // 2- Instance open non-virt null delegate shuffle thunk target method + // 3- Instance open virtual null delegate Virtual-stub dispatch method id + // 4- Static closed null first arg target method null + // 5- Static closed (special sig) null first arg ThisPtrRetBuf precode null + // 6- Static opened null delegate shuffle thunk target method // // Delegate invoke arg count == target method arg count - 2, 3, 6 // Delegate invoke arg count == 1 + target method arg count - 1, 4, 5 @@ -2712,7 +2624,7 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT // 3 - CtorVirtualDispatch // Collectible delegates use the corresponding CtorCollectible* variants. // - // With collectible types, we need to fill the _methodBase field in with a value that represents the LoaderAllocator of the target method + // With collectible types, we need to fill the _invocationList field in with a value that represents the LoaderAllocator of the target method // if the delegate is not a closed instance delegate. // // There are two techniques that will work for this. diff --git a/src/coreclr/vm/comdelegate.h b/src/coreclr/vm/comdelegate.h index 4982f93c418744..fe82249726fb9d 100644 --- a/src/coreclr/vm/comdelegate.h +++ b/src/coreclr/vm/comdelegate.h @@ -59,8 +59,6 @@ class COMDelegate static Stub* GetInvokeMethodStub(EEImplMethodDesc* pMD); static MethodDesc * __fastcall GetMethodDesc(OBJECTREF obj); - static MethodDesc* GetMethodDescForOpenVirtualDelegate(OBJECTREF orDelegate); - static OBJECTREF GetTargetObject(OBJECTREF obj); static BOOL IsTrueMulticastDelegate(OBJECTREF delegate); @@ -114,10 +112,9 @@ extern "C" BOOL QCALLTYPE Delegate_BindToMethodName(QCall::ObjectHandleOnStack d extern "C" BOOL QCALLTYPE Delegate_BindToMethodInfo(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack target, MethodDesc * method, QCall::TypeHandle pMethodType, DelegateBindingFlags flags); -extern "C" void QCALLTYPE Delegate_FindMethodHandle(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack retMethodInfo); - -extern "C" BOOL QCALLTYPE Delegate_InternalEqualMethodHandles(QCall::ObjectHandleOnStack left, QCall::ObjectHandleOnStack right); +extern "C" void QCALLTYPE Delegate_CreateMethodInfo(MethodDesc* methodDesc, QCall::ObjectHandleOnStack retMethodInfo); +extern "C" MethodDesc* QCALLTYPE Delegate_GetMethodDesc(QCall::ObjectHandleOnStack instance); void DistributeEvent(OBJECTREF *pDelegate, OBJECTREF *pDomain); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 5d453720aa0ebb..5bef79a5aa4ac7 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -227,17 +227,20 @@ DEFINE_METHOD(DATE_TIME, LONG_CTOR, .ctor, DEFINE_CLASS(DECIMAL, System, Decimal) DEFINE_METHOD(DECIMAL, CURRENCY_CTOR, .ctor, IM_Currency_RetVoid) -DEFINE_CLASS_U(System, Delegate, NoClass) -DEFINE_FIELD_U(_target, DelegateObject, _target) -DEFINE_FIELD_U(_methodBase, DelegateObject, _methodBase) -DEFINE_FIELD_U(_methodPtr, DelegateObject, _methodPtr) -DEFINE_FIELD_U(_methodPtrAux, DelegateObject, _methodPtrAux) -DEFINE_CLASS(DELEGATE, System, Delegate) -DEFINE_FIELD(DELEGATE, TARGET, _target) -DEFINE_FIELD(DELEGATE, METHOD_PTR, _methodPtr) -DEFINE_FIELD(DELEGATE, METHOD_PTR_AUX, _methodPtrAux) -DEFINE_METHOD(DELEGATE, CONSTRUCT_DELEGATE, DelegateConstruct, IM_Obj_IntPtr_RetVoid) -DEFINE_METHOD(DELEGATE, GET_INVOKE_METHOD, GetInvokeMethod, IM_RetIntPtr) +DEFINE_CLASS_U(System, Delegate, NoClass) +DEFINE_FIELD_U(_invocationList, DelegateObject, _invocationList) +DEFINE_FIELD_U(_target, DelegateObject, _target) +DEFINE_FIELD_U(_methodPtr, DelegateObject, _methodPtr) +DEFINE_FIELD_U(_methodPtrAux, DelegateObject, _methodPtrAux) +DEFINE_FIELD_U(_invocationCount, DelegateObject, _invocationCount) +DEFINE_CLASS(DELEGATE, System, Delegate) +DEFINE_FIELD(DELEGATE, INVOCATION_LIST, _invocationList) +DEFINE_FIELD(DELEGATE, TARGET, _target) +DEFINE_FIELD(DELEGATE, METHOD_PTR, _methodPtr) +DEFINE_FIELD(DELEGATE, METHOD_PTR_AUX, _methodPtrAux) +DEFINE_FIELD(DELEGATE, INVOCATION_COUNT, _invocationCount) +DEFINE_METHOD(DELEGATE, CONSTRUCT_DELEGATE, DelegateConstruct, IM_Obj_IntPtr_RetVoid) +DEFINE_METHOD(DELEGATE, GET_INVOKE_METHOD, GetInvokeMethod, IM_RetIntPtr) DEFINE_CLASS(INT128, System, Int128) DEFINE_CLASS(UINT128, System, UInt128) @@ -584,12 +587,7 @@ DEFINE_CLASS(MODULE, Reflection, RuntimeModule) DEFINE_CLASS(TYPE_BUILDER, ReflectionEmit, TypeBuilder) DEFINE_CLASS(ENUM_BUILDER, ReflectionEmit, EnumBuilder) -DEFINE_CLASS_U(System, MulticastDelegate, DelegateObject) -DEFINE_FIELD_U(_invocationList, DelegateObject, _invocationList) -DEFINE_FIELD_U(_invocationCount, DelegateObject, _invocationCount) DEFINE_CLASS(MULTICAST_DELEGATE, System, MulticastDelegate) -DEFINE_FIELD(MULTICAST_DELEGATE, INVOCATION_LIST, _invocationList) -DEFINE_FIELD(MULTICAST_DELEGATE, INVOCATION_COUNT, _invocationCount) DEFINE_METHOD(MULTICAST_DELEGATE, CTOR_CLOSED, CtorClosed, IM_Obj_IntPtr_RetVoid) DEFINE_METHOD(MULTICAST_DELEGATE, CTOR_CLOSED_STATIC, CtorClosedStatic, IM_Obj_IntPtr_RetVoid) DEFINE_METHOD(MULTICAST_DELEGATE, CTOR_RT_CLOSED, CtorRTClosed, IM_Obj_IntPtr_RetVoid) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 714f41bfa7a742..574022575ad2ab 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -3323,7 +3323,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr if (isOpenVirtual) { - targetMethod = COMDelegate::GetMethodDescForOpenVirtualDelegate(*delegateObj); + targetMethod = (MethodDesc*)(*delegateObj)->GetInvocationCount(); OBJECTREF *pThisArg = LOCAL_VAR_ADDR(callArgsOffset + INTERP_STACK_SLOT_SIZE, OBJECTREF); NULL_CHECK(*pThisArg); targetMethod = CallWithSEHWrapper( diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index a389d56231ba0c..10b76b3a62ef26 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1903,16 +1903,22 @@ HCIMPL2(void, JIT_DelegateProfile32, Object *obj, ICorJitInfo::HandleHistogram32 _ASSERTE(pMT->IsDelegate()); // Resolve method. We handle only the common "direct" delegate as that is - // in any case the only one we can reasonably do GDV for. For instance, - // open delegates are filtered out here, and many cases with inner - // "complicated" logic as well (e.g. static functions, multicast, unmanaged - // functions). - // - MethodDesc* pRecordedMD = (MethodDesc*)DEFAULT_UNKNOWN_HANDLE; + // in any case the only one we can reasonably do GDV for. + // We filter out multicast and unmanaged here. + DELEGATEREF del = (DELEGATEREF)objRef; - if ((del->GetInvocationCount() == 0) && (del->GetMethodPtrAux() == (PCODE)NULL)) + INT_PTR invocationCount = del->GetInvocationCount(); + OBJECTREF invocationList = del->GetInvocationList(); + + MethodDesc* pRecordedMD = (MethodDesc*)DEFAULT_UNKNOWN_HANDLE; + if (((invocationList == NULL) || !invocationList->GetMethodTable()->IsArray()) && (invocationCount != DELEGATE_MARKER_UNMANAGEDFPTR)) { - MethodDesc* pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); + MethodDesc* pMD = (MethodDesc*)invocationCount; + if ((pMD == nullptr) && (del->GetMethodPtrAux() == NULL)) + { + pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); + } + if ((pMD != nullptr) && !pMD->GetLoaderAllocator()->IsCollectible() && !pMD->IsDynamicMethod()) { pRecordedMD = pMD; @@ -1949,16 +1955,22 @@ HCIMPL2(void, JIT_DelegateProfile64, Object *obj, ICorJitInfo::HandleHistogram64 _ASSERTE(pMT->IsDelegate()); // Resolve method. We handle only the common "direct" delegate as that is - // in any case the only one we can reasonably do GDV for. For instance, - // open delegates are filtered out here, and many cases with inner - // "complicated" logic as well (e.g. static functions, multicast, unmanaged - // functions). - // - MethodDesc* pRecordedMD = (MethodDesc*)DEFAULT_UNKNOWN_HANDLE; + // in any case the only one we can reasonably do GDV for. + // We filter out multicast and unmanaged here. + DELEGATEREF del = (DELEGATEREF)objRef; - if ((del->GetInvocationCount() == 0) && (del->GetMethodPtrAux() == (PCODE)NULL)) + INT_PTR invocationCount = del->GetInvocationCount(); + OBJECTREF invocationList = del->GetInvocationList(); + + MethodDesc* pRecordedMD = (MethodDesc*)DEFAULT_UNKNOWN_HANDLE; + if (((invocationList == NULL) || !invocationList->GetMethodTable()->IsArray()) && (invocationCount != DELEGATE_MARKER_UNMANAGEDFPTR)) { - MethodDesc* pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); + MethodDesc* pMD = (MethodDesc*)invocationCount; + if ((pMD == nullptr) && (del->GetMethodPtrAux() == NULL)) + { + pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); + } + if ((pMD != nullptr) && !pMD->GetLoaderAllocator()->IsCollectible() && !pMD->IsDynamicMethod()) { pRecordedMD = pMD; diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 33ec6d6cc7e842..e5544385be0f00 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1743,8 +1743,9 @@ typedef BStrWrapper* BSTRWRAPPEROBJECTREF; #endif // FEATURE_COMINTEROP +#define DELEGATE_MARKER_UNMANAGEDFPTR (-1) -// This class corresponds to System.MulticastDelegate on the managed side. +// This class corresponds to System.Delegate on the managed side. class DelegateObject : public Object { friend class CheckAsmOffsets; @@ -1752,6 +1753,10 @@ class DelegateObject : public Object friend struct ::cdac_data; public: + OBJECTREF GetInvocationList() { LIMITED_METHOD_CONTRACT; return _invocationList; } + void SetInvocationList(OBJECTREF invocationList) { WRAPPER_NO_CONTRACT; SetObjectReference(&_invocationList, invocationList); } + static int GetOffsetOfInvocationList() { LIMITED_METHOD_CONTRACT; return offsetof(DelegateObject, _invocationList); } + OBJECTREF GetTarget() { LIMITED_METHOD_CONTRACT; return _target; } void SetTarget(OBJECTREF target) { WRAPPER_NO_CONTRACT; SetObjectReference(&_target, target); } static int GetOffsetOfTarget() { LIMITED_METHOD_CONTRACT; return offsetof(DelegateObject, _target); } @@ -1764,33 +1769,24 @@ class DelegateObject : public Object void SetMethodPtrAux(PCODE methodPtrAux) { LIMITED_METHOD_CONTRACT; _methodPtrAux = methodPtrAux; } static int GetOffsetOfMethodPtrAux() { LIMITED_METHOD_CONTRACT; return offsetof(DelegateObject, _methodPtrAux); } - OBJECTREF GetInvocationList() { LIMITED_METHOD_CONTRACT; return _invocationList; } - void SetInvocationList(OBJECTREF invocationList) { WRAPPER_NO_CONTRACT; SetObjectReference(&_invocationList, invocationList); } - static int GetOffsetOfInvocationList() { LIMITED_METHOD_CONTRACT; return offsetof(DelegateObject, _invocationList); } - INT_PTR GetInvocationCount() { LIMITED_METHOD_CONTRACT; return _invocationCount; } void SetInvocationCount(INT_PTR invocationCount) { LIMITED_METHOD_CONTRACT; _invocationCount = invocationCount; } static int GetOffsetOfInvocationCount() { LIMITED_METHOD_CONTRACT; return offsetof(DelegateObject, _invocationCount); } - void SetMethodBase(OBJECTREF newMethodBase) { LIMITED_METHOD_CONTRACT; SetObjectReference((OBJECTREF*)&_methodBase, newMethodBase); } - // README: // If you modify the order of these fields, make sure to update the definition in // BCL for this object. private: - // System.Delegate - OBJECTREF _methodBase; + OBJECTREF _invocationList; OBJECTREF _target; PCODE _methodPtr; PCODE _methodPtrAux; - // System.MulticastDelegate - OBJECTREF _invocationList; INT_PTR _invocationCount; }; -#define OFFSETOF__DelegateObject__target (OBJECT_SIZE /* m_pMethTab */ + TARGET_POINTER_SIZE /* _methodBase */) -#define OFFSETOF__DelegateObject__methodPtr (OFFSETOF__DelegateObject__target + TARGET_POINTER_SIZE /* _target */) -#define OFFSETOF__DelegateObject__methodPtrAux (OFFSETOF__DelegateObject__methodPtr + TARGET_POINTER_SIZE /* _methodPtr */) +#define OFFSETOF__DelegateObject__target (OBJECT_SIZE /* m_pMethTab */ + TARGET_POINTER_SIZE /* _invocationList */) +#define OFFSETOF__DelegateObject__methodPtr (OFFSETOF__DelegateObject__target + TARGET_POINTER_SIZE /* _target */) +#define OFFSETOF__DelegateObject__methodPtrAux (OFFSETOF__DelegateObject__methodPtr + TARGET_POINTER_SIZE /* _methodPtr */) template<> struct cdac_data diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 85b240951b7ca1..c7691f66d9ae02 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -107,8 +107,8 @@ static const Entry s_QCall[] = DllImportEntry(Delegate_GetMulticastInvokeSlow) DllImportEntry(Delegate_AdjustTarget) DllImportEntry(Delegate_Construct) - DllImportEntry(Delegate_FindMethodHandle) - DllImportEntry(Delegate_InternalEqualMethodHandles) + DllImportEntry(Delegate_CreateMethodInfo) + DllImportEntry(Delegate_GetMethodDesc) DllImportEntry(Environment_Exit) DllImportEntry(Environment_FailFast) DllImportEntry(Environment_GetProcessorCount) diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index e8f55803ece74a..3d79f096d71d36 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1212,13 +1212,17 @@ BOOL StubLinkStubManager::TraceDelegateObject(BYTE* pbDel, TraceDestination *tra LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: invocationList: %p\n", pbDelInvocationList)); - if (pbDelInvocationList == NULL) + if (pbDelInvocationList == NULL || !(*(MethodTable**)pbDelInvocationList)->IsArray()) { // A null invocationList can be one of the following: - // - Instance closed, Instance open non-virt, Instance open virtual, Static closed, Static opened, Unmanaged FtnPtr - // - Instance open virtual is complex and we need to figure out what to do (TODO). - // For the others the logic is the following: - // if _methodPtrAux is 0 the target is in _methodPtr, otherwise the taret is _methodPtrAux + // - Instance closed + // - Instance open non-virt + // - Instance open virtual + // - Static closed + // - Static opened + // - Instance open virtual + // - Unmanaged FtnPtr + // if _methodPtrAux is 0 the target is in _methodPtr, otherwise the target is _methodPtrAux ppbDest = (BYTE **)(pbDel + DelegateObject::GetOffsetOfMethodPtrAux()); if (*ppbDest == NULL) @@ -1242,39 +1246,6 @@ BOOL StubLinkStubManager::TraceDelegateObject(BYTE* pbDel, TraceDestination *tra return res; } - // invocationList is not null, so it can be one of the following: - // Multicast, Static closed (special sig), Secure - - // rule out the static with special sig - BYTE *pbCount = *(BYTE **)(pbDel + DelegateObject::GetOffsetOfInvocationCount()); - if (pbCount == NULL) - { - // it's a static closed, the target lives in _methodAuxPtr - ppbDest = (BYTE **)(pbDel + DelegateObject::GetOffsetOfMethodPtrAux()); - - if (*ppbDest == NULL) - { - // it's not looking good, bail out - LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: can't trace into it\n")); - return FALSE; - } - - LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: ppbDest: %p *ppbDest:%p\n", ppbDest, *ppbDest)); - - BOOL res = StubManager::TraceStub((PCODE) (*ppbDest), trace); - - LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: res: %d, result type: %d\n", (res ? "true" : "false"), trace->GetTraceType())); - - return res; - } - - MethodTable *pType = *(MethodTable**)pbDelInvocationList; - if (pType->IsDelegate()) - { - // this is a secure delegate. The target is hidden inside this field, so recurse. - return TraceDelegateObject(pbDelInvocationList, trace); - } - // Otherwise, we're going for the first invoke of the multi case. // In order to go to the correct spot, we have just have to fish out // slot 0 of the invocation list, and figure out where that's going to, diff --git a/src/coreclr/vm/virtualcallstub.cpp b/src/coreclr/vm/virtualcallstub.cpp index 0b1567592843c8..e3faa10a51b1d8 100644 --- a/src/coreclr/vm/virtualcallstub.cpp +++ b/src/coreclr/vm/virtualcallstub.cpp @@ -1475,7 +1475,7 @@ extern "C" PCODE CID_VirtualOpenDelegateDispatchWorker(TransitionBlock * pTransi _ASSERTE(!"Throw returned"); } - MethodDesc *pTargetMD = COMDelegate::GetMethodDescForOpenVirtualDelegate(delegateObj); + MethodDesc *pTargetMD = (MethodDesc*)((DELEGATEREF)delegateObj)->GetInvocationCount(); pSDFrame->SetFunction(pTargetMD); pSDFrame->Push(CURRENT_THREAD); diff --git a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs index 6efa9f48554fad..08d9966a550ea5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs @@ -68,17 +68,19 @@ public abstract partial class Delegate : ICloneable, ISerializable public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type target, string method, bool ignoreCase) => CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure: true)!; #if !NATIVEAOT - protected virtual Delegate CombineImpl(Delegate? d) => throw new MulticastNotSupportedException(SR.Multicast_Combine); - - protected virtual Delegate? RemoveImpl(Delegate d) => d.Equals(this) ? null : this; - - public virtual Delegate[] GetInvocationList() => [this]; - /// /// Gets a value that indicates whether the has a single invocation target. /// /// true if the has a single invocation target. - public bool HasSingleTarget => Unsafe.As(this).HasSingleTarget; + public partial bool HasSingleTarget { get; } + + public object? Target => GetTarget(); + + public virtual Delegate[] GetInvocationList() => [this]; + + protected virtual Delegate CombineImpl(Delegate? d) => throw new MulticastNotSupportedException(SR.Multicast_Combine); + + protected virtual Delegate? RemoveImpl(Delegate d) => d.Equals(this) ? null : this; #endif /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index a5ba94532bf8e5..8fd99ae1c64626 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -4803,7 +4803,7 @@ public abstract partial class MulticastDelegate : System.Delegate public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public static bool operator ==(System.MulticastDelegate? d1, System.MulticastDelegate? d2) { throw null; } public static bool operator !=(System.MulticastDelegate? d1, System.MulticastDelegate? d2) { throw null; } - protected sealed override System.Delegate? RemoveImpl(System.Delegate value) { throw null; } + protected sealed override System.Delegate? RemoveImpl(System.Delegate? value) { throw null; } } public sealed partial class MulticastNotSupportedException : System.SystemException { diff --git a/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs index de3120dac128f2..86d602d6772b98 100644 --- a/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs @@ -105,7 +105,7 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al }; } - public object? Target => GetTarget(); + public partial bool HasSingleTarget => Unsafe.As(this).HasSingleTarget; internal virtual object? GetTarget() => _target; diff --git a/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs b/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs index 47fb79aa3cdfa7..240e9f281bdf41 100644 --- a/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs @@ -197,7 +197,7 @@ private static int LastIndexOf(Delegate[] haystack, Delegate[] needle) return -1; } - protected sealed override Delegate? RemoveImpl(Delegate value) + protected sealed override Delegate? RemoveImpl(Delegate? value) { if (value == null) return this;