diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index f0f6f107748e96..29d946f0a759ef 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -66,10 +66,11 @@ Data descriptors used: | `SyncTableEntry` | `SyncBlock` | `SyncBlock` corresponding to the entry | | `ObjectHeader` | `SyncBlockValue` | Sync block value from the object header | | `SyncBlock` | `HashCode` | Hash code stored in the sync block | +| `Delegate` | `HelperObject` | Invocation list for multicast, MethodInfo otherwise | | `Delegate` | `Target` | Bound `this` reference for closed delegates | | `Delegate` | `MethodPtr` | Primary method pointer | | `Delegate` | `MethodPtrAux` | Auxiliary method pointer | -| `Delegate` | `InvocationCount` | Invocation count (non-zero for multicast/wrapper/unmanaged/special delegates) | +| `Delegate` | `ExtraData` | Invocation count for multicast, UnmanagedMarker for unmanaged, MethodDesc otherwise | | `ContinuationObject` | `Next` | Pointer to the next continuation in the linked list | | `ContinuationObject` | `ResumeInfo` | Pointer to the `ResumeInfo` for this suspension point (may be null) | | `ContinuationObject` | `State` | State index identifying the suspension point within the resumed method | @@ -89,6 +90,11 @@ Global variables used: | `SyncBlockHashCodeMask` | uint32 | Mask for extracting the hash code from the sync block value. | | `SyncBlockIndexMask` | uint32 | The mask for sync block index field. | +Contract Constants: +| Name | Type | Purpose | Value | +| --- | --- | --- | --- | +| `UnmanagedMarker` | nint | Sentinel value for detecting unmanaged pointer delegates. | `-1` | + Contracts used: | Contract Name | | --- | @@ -220,15 +226,25 @@ DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = new Data.Delegate(target, address); - // Classify the delegate from its invocation count and auxiliary pointer. - // This does not handle open virtual delegates correctly. - DelegateType delegateType = target.ReadNInt(address + /* Delegate::InvocationCount offset */) switch + // Check for multicast and unmanaged first. + bool isMulticast = false; + TargetPointer helperObject = target.ReadPointer(address + /* Delegate::HelperObject offset */); + if (helperObject != TargetPointer.Null) { - 0 => del.MethodPtrAux == TargetCodePointer.Null - ? DelegateType.Closed - : DelegateType.Open, - _ => DelegateType.Unknown, - }; + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + TargetPointer mt = GetMethodTableAddress(helperObject); + Debug.Assert(mt != TargetPointer.Null); + + isMulticast = rts.IsArray(rts.GetTypeHandle(mt), out _); + } + + const nint UnmanagedMarker = -1; + DelegateType delegateType = DelegateType.Unknown; + if (!isMulticast && target.ReadNInt(address + /* Delegate::ExtraData offset */) != UnmanagedMarker) + { + delegateType = del.MethodPtrAux == TargetCodePointer.Null ? DelegateType.Closed : DelegateType.Open; + } // Pick the bound object and primary entry point based on the classification. // For Closed delegates the target is the bound `this` and MethodPtr is invoked on it. 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 773248fa5a647d..5f62281f3aa994 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -8,19 +8,26 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using System.Runtime.Versioning; 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 + private const nint UnmanagedMarker = -1; + + // This is set under 3 circumstances + // 1. Multicast delegates - object[] + // 2. Method cache - MethodInfo + // 3. Collectible delegates - LoaderAllocator and such internal object? _helperObject; - // _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 + // _target is the object we will invoke on + // 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 @@ -32,6 +39,31 @@ public abstract partial class Delegate : ICloneable, ISerializable // to _methodPtrAux. internal IntPtr _methodPtrAux; + // this stores the multicast count, UnmanagedMarker or target MethodDesc + internal nint _extraData; + + private bool IsUnmanagedFunctionPtr => _extraData == UnmanagedMarker; + + private bool IsClosed => _methodPtrAux == 0; + + public partial bool HasSingleTarget => _helperObject is null || _helperObject.GetType() != typeof(object[]); + + public object? Target => + TryGetInvocations(out ReadOnlySpan invocations) + ? ((Delegate)invocations[^1]).Target + : IsClosed ? _target : null; + + private unsafe MethodDesc* MethodDesc + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(HasSingleTarget); + Debug.Assert(!IsUnmanagedFunctionPtr); + return _extraData != 0 ? (MethodDesc*)_extraData : GetMethodDesc(); + } + } + // This constructor is called from the class generated by the // compiler generated code [RequiresUnreferencedCode("The target method might be removed")] @@ -78,6 +110,58 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al DelegateBindingFlags.CaselessMatching); } + // This method returns the Invocation list of this multicast delegate. + public Delegate[] GetInvocationList() + { + if (!TryGetInvocations(out ReadOnlySpan invocations)) + { + return [this]; + } + + Delegate[] invocationList = new Delegate[invocations.Length]; + for (int i = 0; i < invocations.Length; i++) + { + invocationList[i] = (Delegate)invocations[i]; + } + return invocationList; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryGetInvocations(out ReadOnlySpan invocations) + { + if (HasSingleTarget) + { + invocations = default; + return false; + } + + Debug.Assert(_helperObject is object[]); + object[] invocationList = (object[])_helperObject; + + Debug.Assert(invocationList.Length > 1); + Debug.Assert((uint)invocationList.Length >= (nuint)_extraData); + Debug.Assert(invocationList[0] is MulticastDelegate); + + 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 ((uint)index < (uint)invocations.Length) + return (Delegate)invocations[index]; + } + else if (index == 0) + { + return this; + } + + return null; + } + protected virtual object? DynamicInvokeImpl(object?[]? args) { RuntimeMethodHandleInternal method = new RuntimeMethodHandleInternal(GetInvokeMethod()); @@ -86,105 +170,130 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al return invoke.Invoke(this, BindingFlags.Default, null, args, null); } - public override bool Equals([NotNullWhen(true)] object? obj) + // Equals returns true IIF the delegate is not null and has the + // same target, method and invocation list as this object. + public sealed override unsafe bool Equals([NotNullWhen(true)] object? obj) { - if (obj == null || !InternalEqualTypes(this, obj)) + if (obj == null) return false; - - Delegate d = (Delegate)obj; - - // do an optimistic check first. This is hopefully cheap enough to be worth - if (_target == d._target && _methodPtr == d._methodPtr && _methodPtrAux == d._methodPtrAux) + if (ReferenceEquals(this, obj)) return true; + if (!InternalEqualTypes(this, obj)) + return false; - // even though the fields were not all equals the delegates may still match - // When target carries the delegate itself the 2 targets (delegates) may be different instances - // but the delegates are logically the same - // 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 + // 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 (_methodPtrAux == IntPtr.Zero) + // Check closed delegates first + if (IsClosed) { - if (d._methodPtrAux != IntPtr.Zero) - return false; // different delegate kind + // different delegate kind + if (!other.IsClosed) + return false; - // they are both closed over the first arg - if (_target != d._target) + // different instances + if (_target != other._target) return false; - // fall through method handle check + if (_methodPtr == other._methodPtr) + return true; } else { - if (d._methodPtrAux == IntPtr.Zero) - 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) + // different delegate kinds + if (other.IsClosed) return false; - */ - if (_methodPtrAux == d._methodPtrAux) + // 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; + } - // fall through method handle check - } + // unmanaged + if (IsUnmanagedFunctionPtr) + { + return other.IsUnmanagedFunctionPtr && + _methodPtrAux == other._methodPtrAux; + } - // method ptrs don't match, go down long path + // Under cached interface dispatch we might see the shared CID_VirtualOpenDelegateDispatch stub. + // Fallback to desc comparison in such case for correctness. +#if !FEATURE_CACHED_INTERFACE_DISPATCH + // both delegates are open + if (_methodPtrAux == other._methodPtrAux) + return true; +#endif + } - if (_helperObject is MethodInfo && d._helperObject is MethodInfo) - return _helperObject.Equals(d._helperObject); - else - return InternalEqualMethodHandles(this, d); + // It's possible that the method pointer was JITted in one delegate but not the other. + // In such case the delegates may still be equal, but we need to check the MethodDescs. + return MethodDesc == other.MethodDesc; } - public override unsafe int GetHashCode() + public sealed override unsafe 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)_methodPtr)); - else - return unchecked((int)((long)_methodPtrAux)); - */ + if (TryGetInvocations(out ReadOnlySpan invocations)) + { + int hash = 0; + foreach (MulticastDelegate multicastDelegate in invocations) + { + hash = hash * 33 + multicastDelegate.GetHashCode(); + } + return hash; + } + MethodTable* methodTable = RuntimeHelpers.GetMethodTable(this); #if FEATURE_TYPEEQUIVALENCE - // Type-equivalent delegates from different assemblies must have the same hash code - // when they point to the same method (since Equals returns true for them). - // Exclude the type from the hash code for type-equivalent delegate types. if (methodTable->HasTypeEquivalence) { methodTable = null; } #endif - nint methodTableValue = (nint)methodTable; - if (_methodPtrAux == IntPtr.Zero) - return RuntimeHelpers.GetHashCode(_target) * 33 + methodTableValue.GetHashCode(); - else - return methodTableValue.GetHashCode(); + + nuint targetMethod = IsUnmanagedFunctionPtr ? (nuint)_methodPtrAux : (nuint)MethodDesc; + int hashCode = HashCode.Combine((nuint)methodTable, targetMethod); + if (IsClosed) + { + hashCode += RuntimeHelpers.GetHashCode(_target) * 33; + } + return hashCode; } protected virtual MethodInfo GetMethodImpl() { - if (_helperObject is MethodInfo methodInfo) - { - return methodInfo; - } + return TryGetInvocations(out ReadOnlySpan invocations) + ? ((Delegate)invocations[^1]).Method + : _helperObject as MethodInfo ?? GetMethodImplUncached(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private unsafe MethodInfo GetMethodImplUncached() + { + // 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; + Debug.Assert(!IsUnmanagedFunctionPtr); + bool isStatic = (RuntimeMethodHandle.GetAttributes(method) & MethodAttributes.Static) != 0; if (!isStatic) { - if (_methodPtrAux == IntPtr.Zero) + if (IsClosed) { // 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 @@ -226,8 +335,11 @@ protected virtual MethodInfo GetMethodImpl() } } - _helperObject = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method)!; - return (MethodInfo)_helperObject; + MethodInfo? methodInfo = (MethodInfo?)RuntimeType.GetMethodBase(declaringType, method); + Debug.Assert(methodInfo is not null); + + _helperObject = methodInfo; + return methodInfo; } [RequiresUnreferencedCode("The target method might be removed")] @@ -256,10 +368,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; @@ -291,10 +400,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; @@ -328,10 +434,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; } public static Delegate? CreateDelegate(Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure) @@ -359,10 +462,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; } internal static Delegate CreateDelegateForDynamicMethod(Type type, object? target, RuntimeMethodHandle method, @@ -397,10 +497,7 @@ internal static Delegate CreateDelegateForDynamicMethod(Type type, object? targe { 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; } // @@ -439,6 +536,12 @@ private static MulticastDelegate InternalAlloc(RuntimeType type) return Unsafe.As(RuntimeTypeHandle.InternalAlloc(type)); } + internal static unsafe MulticastDelegate InternalAlloc(MethodTable* type) + { + Debug.Assert(RuntimeTypeHandle.GetRuntimeType(type).IsAssignableTo(typeof(MulticastDelegate))); + return Unsafe.As(RuntimeTypeHandle.InternalAllocNoChecks(type)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe bool InternalEqualTypes(object a, object b) { @@ -514,25 +617,25 @@ internal unsafe IntPtr GetInvokeMethod() return (IntPtr)ptr; } - internal IRuntimeMethodInfo FindMethodHandle() + internal static unsafe IRuntimeMethodInfo CreateMethodInfo(MethodDesc* 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 unsafe partial void CreateMethodInfo(MethodDesc* methodDesc, ObjectHandleOnStack retMethodInfo); - private static bool InternalEqualMethodHandles(Delegate left, Delegate right) + [MethodImpl(MethodImplOptions.NoInlining)] + private unsafe MethodDesc* GetMethodDesc() { - return InternalEqualMethodHandles(ObjectHandleOnStack.Create(ref left), ObjectHandleOnStack.Create(ref right)); + Delegate instance = this; + return GetMethodDesc(ObjectHandleOnStack.Create(ref instance)); } - [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 unsafe partial MethodDesc* GetMethodDesc(ObjectHandleOnStack instance); internal static IntPtr AdjustTarget(object target, IntPtr methodPtr) { @@ -554,6 +657,7 @@ internal void InitializeVirtualCallStub(IntPtr methodPtr) // These flags effect the way BindToMethodInfo and BindToMethodName are allowed to bind a delegate to a target method. Their // values must be kept in sync with the definition in vm\comdelegate.h. + [Flags] internal enum DelegateBindingFlags { StaticMethodOnly = 0x00000001, // Can only bind to static target methods 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 f9e22594e1a3ad..4c1232fd1e1360 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; @@ -13,23 +11,11 @@ namespace System { +#pragma warning disable CS0660, CS0661 // Defining operators, but not overriding Equals/GetHashCode [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] public abstract partial class MulticastDelegate : Delegate { - private object? _invocationList; - - // This is set under 3 circumstances - // 1. Multicast delegate - // 2. Unmanaged function pointer - // 3. Open virtual delegate - private nint _invocationCount; - - private bool IsUnmanagedFunctionPtr() - { - return _invocationCount == -1; - } - [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] public override void GetObjectData(SerializationInfo info, StreamingContext context) @@ -37,104 +23,32 @@ 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) - - if (HasSingleTarget) - { - 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) + private unsafe MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready = false) { // First, allocate a new multicast delegate just like this one, i.e. same type as the this object - MulticastDelegate result = Unsafe.As(RuntimeTypeHandle.InternalAllocNoChecks(RuntimeHelpers.GetMethodTable(this))); + MulticastDelegate result = InternalAlloc(RuntimeHelpers.GetMethodTable(this)); // Performance optimization - if this already points to a true multicast delegate, // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them @@ -149,17 +63,12 @@ private unsafe MulticastDelegate NewMulticastDelegate(object[] invocationList, i result._methodPtrAux = GetInvokeMethod(); } result._target = result; - result._invocationList = invocationList; - result._invocationCount = invocationCount; + result._helperObject = invocationList; + result._extraData = invocationCount; return result; } - internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount) - { - return NewMulticastDelegate(invocationList, invocationCount, false); - } - // This method will combine this delegate with the passed delegate // to form a new delegate. internal new Delegate CombineImpl(Delegate? follow) @@ -174,12 +83,12 @@ internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int inv MulticastDelegate dFollow = (MulticastDelegate)follow; object[]? resultList; int followCount = 1; - object[]? followList = dFollow._invocationList as object[]; + object[]? followList = dFollow._helperObject as object[]; if (followList != null) - followCount = (int)dFollow._invocationCount; + followCount = (int)dFollow._extraData; int resultCount; - if (_invocationList is not object[] invocationList) + if (_helperObject is not object[] invocationList) { resultCount = 1 + followCount; resultList = new object[resultCount]; @@ -195,61 +104,62 @@ internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int inv } return NewMulticastDelegate(resultList, resultCount); } - else + + int invocationCount = (int)_extraData; + 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; + Debug.Assert(_helperObject is object[]); + object[] thisInvocationList = (object[])_helperObject; + int allocCount = thisInvocationList.Length; while (allocCount / 2 >= invocationCount - deleteCount) allocCount /= 2; @@ -285,13 +195,13 @@ 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 = 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) + if (_helperObject is not object[] invocationList) { // they are both not real Multicast if (Equals(v)) @@ -299,50 +209,50 @@ private static bool EqualInvocationLists(object[] a, object[] b, int start, int } else { - int invocationCount = (int)_invocationCount; + int invocationCount = (int)_extraData; for (int i = invocationCount; --i >= 0;) { - if (v.Equals(invocationList[i])) + if (!v.Equals(invocationList[i])) + { + continue; + } + + if (invocationCount == 2) { - 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); - } + // 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 + else if (_helperObject is object[] invocationList) { - if (_invocationList 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--) { - int invocationCount = (int)_invocationCount; - int vInvocationCount = (int)v._invocationCount; - for (int i = invocationCount - vInvocationCount; i >= 0; i--) + if (!EqualInvocationLists(invocationList, vInvocationList, i, vInvocationCount)) + { + continue; + } + + switch (invocationCount - vInvocationCount) { - if (EqualInvocationLists(invocationList, (v._invocationList as object[])!, i, 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: { - if (invocationCount - vInvocationCount == 0) - { - // Special case - no values left - return null; - } - else if (invocationCount - vInvocationCount == 1) - { - // Special case - only one value left, either at the beginning or the end - return (Delegate)invocationList[i != 0 ? 0 : invocationCount - 1]; - } - else - { - object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, vInvocationCount); - return NewMulticastDelegate(list, invocationCount - vInvocationCount, true); - } + object[] list = DeleteFromInvocationList(invocationList, invocationCount, i, + vInvocationCount); + return NewMulticastDelegate(list, invocationCount - vInvocationCount, true); } } } @@ -351,113 +261,6 @@ private static bool EqualInvocationLists(object[] a, object[] b, int start, int return this; } - // This method returns the Invocation list of this multicast delegate. - internal new 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; - } - - internal new bool HasSingleTarget => _invocationList is null; - - // 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() - { - if (IsUnmanagedFunctionPtr()) - return HashCode.Combine(_methodPtr, _methodPtrAux); - - if (_invocationList is not object[] invocationList) - { - return base.GetHashCode(); - } - else - { - int hash = 0; - for (int i = 0; i < (int)_invocationCount; i++) - { - hash = hash * 33 + invocationList[i].GetHashCode(); - } - - return hash; - } - } - - internal new object? Target - { - get - { - Delegate instance = this; - if (_invocationList is object[] invocationList) - { - // Multicast -> return the target of the last delegate in the list - int invocationCount = (int)_invocationCount; - instance = (Delegate)invocationList[invocationCount - 1]; - } - return instance._methodPtrAux == 0 ? instance._target : 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 (_helperObject 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 GetType() for the exact type - RuntimeType reflectedType = (RuntimeType)GetType(); - declaringType = reflectedType; - } - - _helperObject = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method)!; - return (MethodInfo)_helperObject; - } - - return base.GetMethodImpl(); - } - // this should help inlining [DoesNotReturn] [DebuggerNonUserCode] @@ -518,6 +321,7 @@ private void CtorCollectibleClosedStatic(object target, IntPtr methodPtr, IntPtr _target = target; _methodPtr = methodPtr; _helperObject = GCHandle.InternalGet(gchandle); + Debug.Assert(HasSingleTarget); } [DebuggerNonUserCode] @@ -528,6 +332,7 @@ private void CtorCollectibleOpened(object target, IntPtr methodPtr, IntPtr shuff _methodPtr = shuffleThunk; _methodPtrAux = methodPtr; _helperObject = GCHandle.InternalGet(gchandle); + Debug.Assert(HasSingleTarget); } [DebuggerNonUserCode] @@ -537,8 +342,10 @@ private void CtorCollectibleVirtualDispatch(object target, IntPtr methodPtr, Int _target = this; _methodPtr = shuffleThunk; _helperObject = GCHandle.InternalGet(gchandle); + Debug.Assert(HasSingleTarget); InitializeVirtualCallStub(methodPtr); } #pragma warning restore IDE0060 } +#pragma warning restore CS0660, CS0661 } diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index af8673df1c3b0e..a37bacbfd1807b 100644 --- a/src/coreclr/clr.featuredefines.props +++ b/src/coreclr/clr.featuredefines.props @@ -62,6 +62,14 @@ true + + true + + + + true + + $(DefineConstants);FEATURE_JAVAMARSHAL $(DefineConstants);FEATURE_COMWRAPPERS @@ -74,6 +82,7 @@ $(DefineConstants);FEATURE_TYPEEQUIVALENCE $(DefineConstants);FEATURE_INTERPRETER $(DefineConstants);FEATURE_DYNAMIC_CODE_COMPILED + $(DefineConstants);FEATURE_CACHED_INTERFACE_DISPATCH $(DefineConstants);FEATURE_PORTABLE_ENTRYPOINTS $(DefineConstants);FEATURE_PORTABLE_HELPERS $(DefineConstants);PROFILING_SUPPORTED diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index bf335b64142e7f..5f96d98b6e228d 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3174,22 +3174,14 @@ 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 extraData = pDelObj->GetExtraData(); + OBJECTREF helperObject = pDelObj->GetHelperObject(); + if (((helperObject == NULL) || !helperObject->GetMethodTable()->IsArray()) && (extraData != DELEGATE_MARKER_UNMANAGEDFPTR)) { - // If this delegate points to a static function or this is a open virtual delegate, this should be non-null - // This does not handle open virtual delegates correctly. + // If this delegate is open, this should be non-null TADDR targetMethodPtr = PCODEToPINSTR(pDelObj->GetMethodPtrAux()); - if (targetMethodPtr == (TADDR)NULL) - { - // Static extension methods, other closed static delegates, and instance delegates fall into this category. - delegateType = DelegateType::kClosedDelegate; - } - else - { - delegateType = DelegateType::kOpenDelegate; - } + delegateType = targetMethodPtr == (TADDR)NULL ? DelegateType::kClosedDelegate : DelegateType::kOpenDelegate; } return delegateType; } diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index ccf44cc6937c4e..f71bb7ea5d3982 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -8262,7 +8262,7 @@ void DebuggerStepper::TriggerMulticastDelegate(DELEGATEREF pDel, INT32 delegateC TraceDestination trace; FramePointer fp = LEAF_MOST_FRAME; - PTRARRAYREF pDelInvocationList = (PTRARRAYREF) pDel->GetInvocationList(); + PTRARRAYREF pDelInvocationList = (PTRARRAYREF) pDel->GetHelperObject(); DELEGATEREF pCurrentInvokeDel = (DELEGATEREF) pDelInvocationList->GetAt(delegateCount); StubLinkStubManager::TraceDelegateObject((BYTE*)OBJECTREFToObject(pCurrentInvokeDel), &trace); 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 d0a9087237bb94..9252961faeb0ea 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs @@ -694,7 +694,7 @@ public Delegate[] GetInvocationList() return new Delegate[] { this }; } - public override bool Equals([NotNullWhen(true)] object? obj) + public sealed override bool Equals([NotNullWhen(true)] object? obj) { if (obj == null) return false; @@ -749,7 +749,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) return object.ReferenceEquals(_target, d._target); } - public override int GetHashCode() + public sealed override int GetHashCode() { if (_helperObject is Wrapper[] invocationList) { diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index ec6d9f4277c91e..b460e0e68bdbe9 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -1228,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 { @@ -1288,8 +1287,11 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, (*pRefThis)->SetMethodPtr(pTargetCode); } + (*pRefThis)->SetExtraData((INT_PTR)pTargetMethod); + LoaderAllocator *pLoaderAllocator = pTargetMethod->GetLoaderAllocator(); + _ASSERTE((*pRefThis)->GetHelperObject() == NULL); if (pLoaderAllocator->IsCollectible()) (*pRefThis)->SetHelperObject(pLoaderAllocator->GetExposedObject()); } @@ -1323,7 +1325,7 @@ LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj) // If we are a delegate originally created from an unmanaged function pointer, we will simply return // that function pointer. - if (DELEGATE_MARKER_UNMANAGEDFPTR == pDelegate->GetInvocationCount()) + if (DELEGATE_MARKER_UNMANAGEDFPTR == pDelegate->GetExtraData()) { pCode = pDelegate->GetMethodPtrAux(); } @@ -1373,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( @@ -1508,7 +1510,7 @@ OBJECTREF COMDelegate::ConvertToDelegate(LPVOID pCallback, MethodTable* pMT) delObj->SetMethodPtrAux((PCODE)pCallback); // Also, mark this delegate as an unmanaged function pointer wrapper. - delObj->SetInvocationCount(DELEGATE_MARKER_UNMANAGEDFPTR); + delObj->SetExtraData(DELEGATE_MARKER_UNMANAGEDFPTR); } return delObj; @@ -1582,7 +1584,7 @@ extern "C" void QCALLTYPE Delegate_InitializeVirtualCallStub(QCall::ObjectHandle DELEGATEREF refThis = (DELEGATEREF)d.Get(); refThis->SetMethodPtrAux(target); - refThis->SetInvocationCount((INT_PTR)(void*)pMeth); + refThis->SetExtraData((INT_PTR)(void*)pMeth); END_QCALL; } @@ -1705,6 +1707,7 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q if (!isStatic) methodArgCount++; // count 'this' + _ASSERTE(refThis->GetHelperObject() == NULL); if (pMeth->GetLoaderAllocator()->IsCollectible()) refThis->SetHelperObject(pMeth->GetLoaderAllocator()->GetExposedObject()); @@ -1723,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 { @@ -1780,24 +1782,13 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q refThis->SetMethodPtr((PCODE)(void *)method); } + refThis->SetExtraData((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) +MethodDesc* COMDelegate::GetMethodDesc(OBJECTREF orDelegate) { CONTRACTL { @@ -1811,20 +1802,19 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) DELEGATEREF thisDel = (DELEGATEREF) orDelegate; - INT_PTR count = thisDel->GetInvocationCount(); - if (count != 0) + INT_PTR extraData = thisDel->GetExtraData(); + if (extraData != 0) { // this is one of the following: - // - multicast - _invocationList is Array && _invocationCount != 0 - // - unamanaged ftn ptr - _invocationList == null && _invocationCount == -1 - // - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc) + // - multicast - _helperObject is Array && _extraData != 0 + // - unmanaged ftn ptr - _helperObject == null && _extraData == -1 + // - MethodDesc already cached // we return the method desc for the invoke for the first two cases - OBJECTREF invocationList = thisDel->GetInvocationList(); - if (invocationList != NULL || count == DELEGATE_MARKER_UNMANAGEDFPTR) + if (!HasSingleTarget(thisDel) || extraData == DELEGATE_MARKER_UNMANAGEDFPTR) return FindDelegateInvokeMethod(thisDel->GetMethodTable()); - return GetMethodDescForOpenVirtualDelegate(thisDel); + return (MethodDesc*)extraData; } // Next, check for an open delegate @@ -1837,32 +1827,47 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) MethodDesc *pMethodHandle = NonVirtualEntry2MethodDesc(code); _ASSERTE(pMethodHandle); + + thisDel->SetExtraData((INT_PTR)pMethodHandle); return pMethodHandle; } -BOOL COMDelegate::IsTrueMulticastDelegate(OBJECTREF delegate) +MethodDesc* COMDelegate::GetMethodDescForOpenVirtualDelegate(DELEGATEREF delegate) { CONTRACTL { - THROWS; + NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; } CONTRACTL_END; - BOOL isMulticast = FALSE; + _ASSERTE(HasSingleTarget(delegate)); + + INT_PTR extraData = delegate->GetExtraData(); - size_t invocationCount = ((DELEGATEREF)delegate)->GetInvocationCount(); - if (invocationCount) + _ASSERTE(extraData != DELEGATE_MARKER_UNMANAGEDFPTR); + + return (MethodDesc*)extraData; +} + +BOOL COMDelegate::HasSingleTarget(DELEGATEREF delegate) +{ + CONTRACTL { - OBJECTREF invocationList = ((DELEGATEREF)delegate)->GetInvocationList(); - if (invocationList != NULL) - { - isMulticast = TRUE; - } + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + OBJECTREF helperObject = delegate->GetHelperObject(); + if (helperObject == NULL) + { + return true; } - return isMulticast; + return !helperObject->GetMethodTable()->IsArray(); } // Get the cpu stub for a delegate invoke. @@ -1959,7 +1964,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; @@ -1967,30 +1972,28 @@ 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* pMD = nullptr; BEGIN_QCALL; GCX_COOP(); - MethodDesc* pMDLeft = COMDelegate::GetMethodDesc(left.Get()); - MethodDesc* pMDRight = COMDelegate::GetMethodDesc(right.Get()); - fRet = pMDLeft == pMDRight; + pMD = COMDelegate::GetMethodDesc(instance.Get()); END_QCALL; - return fRet; + return pMD; } FCIMPL1(MethodDesc*, COMDelegate::GetInvokeMethod, MethodTable* pDelegateMT) @@ -2071,7 +2074,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__HELPER_OBJECT))); pCode->EmitLDLOC(dwLoopCounterNum); pCode->EmitLDELEM_REF(); @@ -2092,10 +2095,10 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat pCode->EmitADD(); pCode->EmitSTLOC(dwLoopCounterNum); - // compare LoopCounter with InvocationCount. If less then branch to nextDelegate + // compare LoopCounter with _extraData. 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__EXTRA_DATA))); pCode->EmitBLT(nextDelegate); // load the return value. return value from the last delegate call is returned @@ -2617,14 +2620,17 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT // DELEGATE KINDS TABLE // - // _target _methodPtr _methodPtrAux _invocationList _invocationCount + // _helperObject _target _methodPtr _methodPtrAux _extraData // - // 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 method info 'this' ptr target method null method desc cache + // 2- Instance open non-virt method info delegate shuffle thunk target method method desc cache + // 3- Instance open virtual method info delegate shuffle thunk Virtual call stub/ method desc + // with FEATURE_CACHED_INTERFACE_DISPATCH: CID_VirtualOpenDelegateDispatch + // 4- Static closed method info first arg target method null method desc cache + // 5- Static closed (retbuf) method info first arg ThisPtrRetBuf precode null method desc cache + // 6- Static open method info delegate shuffle thunk target method method desc cache + // 7- Unmanaged null delegate marshalling stub unmanaged pointer DELEGATE_MARKER_UNMANAGEDFPTR + // 8- Multicast invocation list delegate multicast stub invoke stub invocation count // // Delegate invoke arg count == target method arg count - 2, 3, 6 // Delegate invoke arg count == 1 + target method arg count - 1, 4, 5 @@ -2634,6 +2640,7 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT // 3 - CtorVirtualDispatch // 4 - CtorClosedStatic // 5 - Retbuf static closed form (not differentiated on this fast path; see TODO below) + // 7, 8 - Not handled here // Collectible delegates use the corresponding CtorCollectible* variants. // // With collectible types, we need to fill the _helperObject field in with a value that represents the LoaderAllocator of the target method diff --git a/src/coreclr/vm/comdelegate.h b/src/coreclr/vm/comdelegate.h index 03a84fabf9f6da..102d157d3782f7 100644 --- a/src/coreclr/vm/comdelegate.h +++ b/src/coreclr/vm/comdelegate.h @@ -58,9 +58,10 @@ class COMDelegate // Get the cpu stub for a delegate invoke. static Stub* GetInvokeMethodStub(EEImplMethodDesc* pMD); - static MethodDesc * __fastcall GetMethodDesc(OBJECTREF obj); - static MethodDesc* GetMethodDescForOpenVirtualDelegate(OBJECTREF orDelegate); - static BOOL IsTrueMulticastDelegate(OBJECTREF delegate); + static MethodDesc* GetMethodDesc(OBJECTREF obj); + static MethodDesc* GetMethodDescForOpenVirtualDelegate(DELEGATEREF delegate); + + static BOOL HasSingleTarget(DELEGATEREF delegate); // Throw if the method violates any usage restrictions // for UnmanagedCallersOnlyAttribute. @@ -112,10 +113,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 7020945877cc67..6767c90782a87e 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(_helperObject, DelegateObject, _helperObject) -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(_helperObject, DelegateObject, _helperObject) +DEFINE_FIELD_U(_target, DelegateObject, _target) +DEFINE_FIELD_U(_methodPtr, DelegateObject, _methodPtr) +DEFINE_FIELD_U(_methodPtrAux, DelegateObject, _methodPtrAux) +DEFINE_FIELD_U(_extraData, DelegateObject, _extraData) +DEFINE_CLASS(DELEGATE, System, Delegate) +DEFINE_FIELD(DELEGATE, HELPER_OBJECT, _helperObject) +DEFINE_FIELD(DELEGATE, TARGET, _target) +DEFINE_FIELD(DELEGATE, METHOD_PTR, _methodPtr) +DEFINE_FIELD(DELEGATE, METHOD_PTR_AUX, _methodPtrAux) +DEFINE_FIELD(DELEGATE, EXTRA_DATA, _extraData) +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) @@ -585,12 +588,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/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 1a5113f15e4c3c..a856c48b43bf66 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -214,10 +214,11 @@ CDAC_TYPE_END(Array) CDAC_TYPE_BEGIN(Delegate) CDAC_TYPE_SIZE(sizeof(DelegateObject)) +CDAC_TYPE_FIELD(Delegate, T_POINTER, HelperObject, cdac_data::HelperObject) CDAC_TYPE_FIELD(Delegate, T_POINTER, Target, cdac_data::Target) CDAC_TYPE_FIELD(Delegate, T_POINTER, MethodPtr, cdac_data::MethodPtr) CDAC_TYPE_FIELD(Delegate, T_POINTER, MethodPtrAux, cdac_data::MethodPtrAux) -CDAC_TYPE_FIELD(Delegate, T_NINT, InvocationCount, cdac_data::InvocationCount) +CDAC_TYPE_FIELD(Delegate, T_NINT, ExtraData, cdac_data::ExtraData) CDAC_TYPE_END(Delegate) CDAC_TYPE_BEGIN(TypedByRef) diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 42b5bc31a938f9..52241ce4b506e6 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -1904,17 +1904,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 extraData = del->GetExtraData(); + + MethodDesc* pRecordedMD = (MethodDesc*)DEFAULT_UNKNOWN_HANDLE; + if (COMDelegate::HasSingleTarget(del) && (extraData != DELEGATE_MARKER_UNMANAGEDFPTR)) { - MethodDesc* pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); - if ((pMD != nullptr) && !pMD->GetLoaderAllocator()->IsCollectible() && !pMD->IsDynamicMethod()) + MethodDesc* pMD = NULL; + if (del->GetMethodPtrAux() == (PCODE)NULL) + { + pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); + } + + if ((pMD != NULL) && !pMD->GetLoaderAllocator()->IsCollectible() && !pMD->IsDynamicMethod()) { pRecordedMD = pMD; } @@ -1950,17 +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 extraData = del->GetExtraData(); + + MethodDesc* pRecordedMD = (MethodDesc*)DEFAULT_UNKNOWN_HANDLE; + if (COMDelegate::HasSingleTarget(del) && (extraData != DELEGATE_MARKER_UNMANAGEDFPTR)) { - MethodDesc* pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); - if ((pMD != nullptr) && !pMD->GetLoaderAllocator()->IsCollectible() && !pMD->IsDynamicMethod()) + MethodDesc* pMD = NULL; + if (del->GetMethodPtrAux() == (PCODE)NULL) + { + pMD = NonVirtualEntry2MethodDesc(del->GetMethodPtr()); + } + + if ((pMD != NULL) && !pMD->GetLoaderAllocator()->IsCollectible() && !pMD->IsDynamicMethod()) { pRecordedMD = pMD; } diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index c8da677080966e..a9710bb168cf94 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1745,7 +1745,7 @@ typedef BStrWrapper* BSTRWRAPPEROBJECTREF; #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; @@ -1753,6 +1753,10 @@ class DelegateObject : public Object friend struct ::cdac_data; public: + OBJECTREF GetHelperObject() { LIMITED_METHOD_CONTRACT; return _helperObject; } + void SetHelperObject(OBJECTREF helperObject) { WRAPPER_NO_CONTRACT; SetObjectReference(&_helperObject, helperObject); } + static int GetOffsetOfHelperObject() { LIMITED_METHOD_CONTRACT; return offsetof(DelegateObject, _helperObject); } + 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); } @@ -1765,41 +1769,33 @@ 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 SetHelperObject(OBJECTREF newHelperObject) { LIMITED_METHOD_CONTRACT; SetObjectReference((OBJECTREF*)&_helperObject, newHelperObject); } + INT_PTR GetExtraData() { LIMITED_METHOD_CONTRACT; return _extraData; } + void SetExtraData(INT_PTR extraData) { LIMITED_METHOD_CONTRACT; _extraData = extraData; } + static int GetOffsetOfExtraData() { LIMITED_METHOD_CONTRACT; return offsetof(DelegateObject, _extraData); } // README: // If you modify the order of these fields, make sure to update the definition in // BCL for this object. private: - // System.Delegate OBJECTREF _helperObject; OBJECTREF _target; PCODE _methodPtr; PCODE _methodPtrAux; - // System.MulticastDelegate - OBJECTREF _invocationList; - INT_PTR _invocationCount; + INT_PTR _extraData; }; -#define OFFSETOF__DelegateObject__target (OBJECT_SIZE /* m_pMethTab */ + TARGET_POINTER_SIZE /* _helperObject */) -#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 /* _helperObject */) +#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 { + static constexpr size_t HelperObject = offsetof(DelegateObject, _helperObject); static constexpr size_t Target = offsetof(DelegateObject, _target); static constexpr size_t MethodPtr = offsetof(DelegateObject, _methodPtr); static constexpr size_t MethodPtrAux = offsetof(DelegateObject, _methodPtrAux); - static constexpr size_t InvocationCount = offsetof(DelegateObject, _invocationCount); + static constexpr size_t ExtraData = offsetof(DelegateObject, _extraData); }; #ifdef USE_CHECKED_OBJECTREFS diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index d73f2cecae953b..eea013be839f29 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -111,8 +111,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 6533c427c4ef59..d874b9614aaeaf 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -463,7 +463,7 @@ BOOL StubManager::CheckIsStub_Worker(PCODE stubStartAddress) EX_TRY #endif { - SUPPORTS_DAC; + SUPPORTS_DAC; #ifndef DACCESS_COMPILE // Use CheckIsStub_Internal may AV. That's ok. @@ -1208,17 +1208,20 @@ BOOL StubLinkStubManager::TraceDelegateObject(BYTE* pbDel, TraceDestination *tra // If we got here, then we're here b/c we're at the start of a delegate stub // need to figure out the kind of delegates we are dealing with. - BYTE *pbDelInvocationList = *(BYTE **)(pbDel + DelegateObject::GetOffsetOfInvocationList()); + BYTE *pbDelInvocationList = *(BYTE **)(pbDel + DelegateObject::GetOffsetOfHelperObject()); 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 open + // - Unmanaged FtnPtr + // if _methodPtrAux is 0 the target is in _methodPtr, otherwise the target is _methodPtrAux ppbDest = (BYTE **)(pbDel + DelegateObject::GetOffsetOfMethodPtrAux()); if (*ppbDest == NULL) @@ -1235,9 +1238,9 @@ BOOL StubLinkStubManager::TraceDelegateObject(BYTE* pbDel, TraceDestination *tra LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: ppbDest: %p *ppbDest:%p\n", ppbDest, *ppbDest)); - BOOL res = StubManager::TraceStub((PCODE) (*ppbDest), trace); + BOOL res = StubManager::TraceStub((PCODE) *ppbDest, trace); - LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: res: %s, result type: %d\n", (res ? "true" : "false"), trace->GetTraceType())); + LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: res: %s, result type: %d\n", res ? "true" : "false", trace->GetTraceType())); return res; } @@ -1246,7 +1249,7 @@ BOOL StubLinkStubManager::TraceDelegateObject(BYTE* pbDel, TraceDestination *tra // 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, // then put a breakpoint there. - pbDel = *(BYTE**)(((ArrayBase *)pbDelInvocationList)->GetDataPtr()); + pbDel = *(BYTE**)((ArrayBase *)pbDelInvocationList)->GetDataPtr(); return TraceDelegateObject(pbDel, trace); } diff --git a/src/coreclr/vm/virtualcallstub.cpp b/src/coreclr/vm/virtualcallstub.cpp index 0b1567592843c8..d0d194b260f4b9 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 = COMDelegate::GetMethodDescForOpenVirtualDelegate((DELEGATEREF)delegateObj); 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 7e14c44771c777..6c1f1d132a4258 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs @@ -73,15 +73,11 @@ public abstract partial class Delegate : ICloneable, ISerializable protected Delegate? RemoveImpl(Delegate? d) => Unsafe.As(this).RemoveImpl(d); - public Delegate[] GetInvocationList() => Unsafe.As(this).GetInvocationList(); - /// /// 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 object? Target => Unsafe.As(this).Target; + public partial bool HasSingleTarget { get; } internal object GetTargetForSingleCastInstanceDelegate() { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs index f9d54aea1c5d7a..6494af3a1341cd 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs @@ -521,6 +521,21 @@ public static void SameGenericMethodObtainedViaDelegateAndReflectionAreSameForSt Assert.Equal(m1.MethodHandle.Value, m2.MethodHandle.Value); } + [Fact] + public static void DifferentOpenVirtualDelegates() + { + MethodInfo m1 = typeof(OpenVirtualClass).GetMethod(nameof(OpenVirtualClass.M1), BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo m2 = typeof(OpenVirtualClass).GetMethod(nameof(OpenVirtualClass.M2), BindingFlags.Instance | BindingFlags.NonPublic); + + Delegate a = m1.CreateDelegate>(); + Delegate b = m2.CreateDelegate>(); + + Assert.False(a.Equals(b)); + + Assert.Equal(m1, a.Method); + Assert.Equal(m2, b.Method); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsTypeEquivalenceSupported))] public static void TypeEquivalentDelegatesPointingToSameMethod_AreEqualAndHaveSameHashCode() { @@ -550,6 +565,12 @@ class ClassG { internal void M() { } } struct StructG { internal void M() { } } + class OpenVirtualClass + { + internal virtual void M1() { } + internal virtual void M2() { } + } + private delegate void IntIntDelegate(int expected, int actual); private delegate void IntIntDelegateWithDefault(int expected, int actual = 7); 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 fa749f1e9a6b64..849ac3c54a1796 100644 --- a/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs @@ -105,8 +105,14 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al }; } + public partial bool HasSingleTarget => Unsafe.As(this).HasSingleTarget; + + public object? Target => Unsafe.As(this).Target; + internal virtual object? GetTarget() => _target; + public Delegate[] GetInvocationList() => Unsafe.As(this).GetInvocationList(); + public static Delegate CreateDelegate(Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure) { return CreateDelegate(type, firstArgument, method, throwOnBindFailure, true)!; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs index 6607f78295f1d5..54a5cec221efff 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs @@ -9,6 +9,8 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal readonly struct Object_1 : IObject { + private const long UnmanagedMarker = -1; + private readonly Target _target; private readonly ulong _methodTableOffset; private readonly byte _objectToMethodTableUnmask; @@ -163,15 +165,22 @@ public DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = _target.ProcessedData.GetOrAdd(address); - // Classify by invocation count first to handle multicast and unmanaged. - // This does not handle open virtual delegates correctly. + // Check for multicast and unmanaged first. + bool isMulticast = false; + if (del.HelperObject != TargetPointer.Null) + { + IRuntimeTypeSystem typeSystemContract = _target.Contracts.RuntimeTypeSystem; + + TargetPointer mt = GetMethodTableAddress(del.HelperObject); + Debug.Assert(mt != TargetPointer.Null); + + isMulticast = typeSystemContract.IsArray(typeSystemContract.GetTypeHandle(mt), out _); + } + DelegateType delegateType = DelegateType.Unknown; - if (del.InvocationCount.Value == 0) + if (!isMulticast && del.ExtraData.Value != UnmanagedMarker) { - if (del.MethodPtrAux == TargetCodePointer.Null) - delegateType = DelegateType.Closed; - else - delegateType = DelegateType.Open; + delegateType = del.MethodPtrAux == TargetCodePointer.Null ? DelegateType.Closed : DelegateType.Open; } (TargetPointer targetObject, TargetCodePointer targetMethodPtr) = delegateType switch diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs index 1b87c4beb4db0a..bf17bcf89f1057 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs @@ -6,8 +6,9 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; [CdacType(nameof(DataType.Delegate))] internal sealed partial class Delegate : IData { + [Field] public TargetPointer HelperObject { get; } [Field] public TargetPointer Target { get; } [Field] public TargetCodePointer MethodPtr { get; } [Field] public TargetCodePointer MethodPtrAux { get; } - [Field] public TargetNInt InvocationCount { get; } + [Field] public TargetNInt ExtraData { get; } } diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs index e92b5d34fb22bf..28f227e4f336b5 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs @@ -144,20 +144,22 @@ public ulong Object internal sealed class MockDelegateObjectData : TypedView { public const string MethodTableFieldName = "m_pMethTab"; + public const string HelperObjectFieldName = "HelperObject"; public const string TargetFieldName = "Target"; public const string MethodPtrFieldName = "MethodPtr"; public const string MethodPtrAuxFieldName = "MethodPtrAux"; - public const string InvocationCountFieldName = "InvocationCount"; + public const string ExtraDataFieldName = "ExtraData"; public static Layout CreateLayout(MockTarget.Architecture architecture) { SequentialLayoutBuilder builder = new("Delegate", architecture); return builder .AddPointerField(MethodTableFieldName) + .AddPointerField(HelperObjectFieldName) .AddPointerField(TargetFieldName) .AddPointerField(MethodPtrFieldName) .AddPointerField(MethodPtrAuxFieldName) - .AddNIntField(InvocationCountFieldName) + .AddNIntField(ExtraDataFieldName) .Build(); } @@ -167,6 +169,12 @@ public ulong MethodTable set => WritePointerField(MethodTableFieldName, value); } + public ulong HelperObject + { + get => ReadPointerField(HelperObjectFieldName); + set => WritePointerField(HelperObjectFieldName, value); + } + public ulong Target { get => ReadPointerField(TargetFieldName); @@ -185,12 +193,12 @@ public ulong MethodPtrAux set => WritePointerField(MethodPtrAuxFieldName, value); } - public long InvocationCount + public long ExtraData { // Stored at pointer width; on 32-bit, WritePointer truncates the upper bits // so the signed bit pattern of `value` is preserved (e.g. -1 → 0xFFFFFFFF). - get => unchecked((long)ReadPointerField(InvocationCountFieldName)); - set => WritePointerField(InvocationCountFieldName, unchecked((ulong)value)); + get => unchecked((long)ReadPointerField(ExtraDataFieldName)); + set => WritePointerField(ExtraDataFieldName, unchecked((ulong)value)); } } @@ -412,7 +420,7 @@ internal ulong AddArrayObject(Array array) return fragment.Address; } - internal ulong AddDelegateObject(ulong methodTable, ulong target, ulong methodPtr, ulong methodPtrAux, long invocationCount) + internal ulong AddDelegateObject(ulong methodTable, ulong target, ulong methodPtr, ulong methodPtrAux, long extraData) { MockMemorySpace.HeapFragment fragment = ManagedObjectAllocator.Allocate((uint)DelegateLayout.Size, $"Delegate : MT = '{methodTable}'"); MockDelegateObjectData mockDelegate = DelegateLayout.Create(fragment); @@ -420,7 +428,7 @@ internal ulong AddDelegateObject(ulong methodTable, ulong target, ulong methodPt mockDelegate.Target = target; mockDelegate.MethodPtr = methodPtr; mockDelegate.MethodPtrAux = methodPtrAux; - mockDelegate.InvocationCount = invocationCount; + mockDelegate.ExtraData = extraData; return fragment.Address; } diff --git a/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs b/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs index 8e5d367eebdabd..aec9e6e73763d1 100644 --- a/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ObjectTests.cs @@ -383,7 +383,7 @@ public void GetDelegateInfo_Closed(MockTarget.Architecture arch) target: TestTarget, methodPtr: TestMethodPtr, methodPtrAux: 0, - invocationCount: 0); + extraData: 0); }); DelegateInfo info = contract.GetDelegateInfo(delegateAddress); @@ -411,7 +411,7 @@ public void GetDelegateInfo_Open(MockTarget.Architecture arch) target: 0, methodPtr: TestMethodPtr, methodPtrAux: TestMethodPtrAux, - invocationCount: 0); + extraData: 0); }); DelegateInfo info = contract.GetDelegateInfo(delegateAddress); @@ -423,11 +423,11 @@ public void GetDelegateInfo_Open(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetDelegateInfo_Multicast(MockTarget.Architecture arch) + public void GetDelegateInfo_Unmanaged(MockTarget.Architecture arch) { const ulong TestMethodTable = 0x00000000_10000200; const ulong TestMethodPtr = 0x00000000_aaaa0000; - const long TestInvocationCount = 3; + const long TestExtraData = -1; TargetPointer delegateAddress = default; IObject contract = CreateObjectContract( @@ -439,7 +439,7 @@ public void GetDelegateInfo_Multicast(MockTarget.Architecture arch) target: 0, methodPtr: TestMethodPtr, methodPtrAux: 0, - invocationCount: TestInvocationCount); + extraData: TestExtraData); }); DelegateInfo info = contract.GetDelegateInfo(delegateAddress);