Skip to content

Commit

Permalink
[NativeAOT] Delegate bug fixes (#99185)
Browse files Browse the repository at this point in the history
- Fix Delegate.Method and Delegate.Target for marshalled delegates
- Add tests and fixes for various delegate corner case behaviors
- Delete runtime test for GetInvocationList since there is a better test coverage for this API under libraries
- Delete `m` prefix on System.Delegate member fields

Fixes dotnet/runtimelab#164
  • Loading branch information
jkotas authored Mar 9, 2024
1 parent 93e35a9 commit cc17166
Show file tree
Hide file tree
Showing 26 changed files with 415 additions and 620 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ public static Delegate CreateDelegate(RuntimeTypeHandle typeHandleForDelegate, I
}

//
// Helper to extract the artifact that uniquely identifies a method in the runtime mapping tables.
// Helper to extract the artifact that identifies a reflectable delegate target in the runtime mapping tables.
//
public static IntPtr GetDelegateLdFtnResult(Delegate d, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver, out bool isInterpreterEntrypoint)
public static IntPtr GetDelegateLdFtnResult(Delegate d, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver)
{
return d.GetFunctionPointer(out typeOfFirstParameterIfInstanceDelegate, out isOpenResolver, out isInterpreterEntrypoint);
return d.GetDelegateLdFtnResult(out typeOfFirstParameterIfInstanceDelegate, out isOpenResolver);
}

// Low level method that returns the loaded modules as array. ReadOnlySpan returning overload
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,16 @@ public static unsafe bool Compare(IntPtr functionPointerA, IntPtr functionPointe

return pointerDefA->MethodFunctionPointer == pointerDefB->MethodFunctionPointer;
}

public static unsafe int GetHashCode(IntPtr functionPointer)
{
if (!IsGenericMethodPointer(functionPointer))
{
return functionPointer.GetHashCode();
}

GenericMethodDescriptor* pointerDef = ConvertToGenericDescriptor(functionPointer);
return pointerDef->GetHashCode();
}
}
}
470 changes: 221 additions & 249 deletions src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,9 @@ internal abstract class NativeFunctionPointerWrapper
{
public NativeFunctionPointerWrapper(IntPtr nativeFunctionPointer)
{
m_nativeFunctionPointer = nativeFunctionPointer;
NativeFunctionPointer = nativeFunctionPointer;
}

private IntPtr m_nativeFunctionPointer;

public IntPtr NativeFunctionPointer
{
get { return m_nativeFunctionPointer; }
}
public IntPtr NativeFunctionPointer { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static unsafe IntPtr GetFunctionPointerForDelegate(Delegate del)
throw new ArgumentException(SR.Argument_NeedNonGenericType, "delegate");
#pragma warning restore CA2208

NativeFunctionPointerWrapper? fpWrapper = del.Target as NativeFunctionPointerWrapper;
NativeFunctionPointerWrapper? fpWrapper = del.TryGetNativeFunctionPointerWrapper();
if (fpWrapper != null)
{
//
Expand Down Expand Up @@ -104,64 +104,71 @@ internal unsafe struct ThunkContextData
public IntPtr FunctionPtr; // Function pointer for open static delegates
}

internal sealed class PInvokeDelegateThunk
internal sealed unsafe class PInvokeDelegateThunk
{
public IntPtr Thunk; // Thunk pointer
public IntPtr ContextData; // ThunkContextData pointer which will be stored in the context slot of the thunk
public readonly IntPtr Thunk; // Thunk pointer
public readonly IntPtr ContextData; // ThunkContextData pointer which will be stored in the context slot of the thunk

public PInvokeDelegateThunk(Delegate del)
{

Thunk = RuntimeAugments.AllocateThunk(s_thunkPoolHeap);
Debug.Assert(Thunk != IntPtr.Zero);

if (Thunk == IntPtr.Zero)
{
// We've either run out of memory, or failed to allocate a new thunk due to some other bug. Now we should fail fast
Environment.FailFast("Insufficient number of thunks.");
throw new OutOfMemoryException();
}
else
{
//
// Allocate unmanaged memory for GCHandle of delegate and function pointer of open static delegate
// We will store this pointer on the context slot of thunk data
//
unsafe
{
ContextData = (IntPtr)NativeMemory.Alloc((nuint)(2 * IntPtr.Size));

ThunkContextData* thunkData = (ThunkContextData*)ContextData;
//
// For open static delegates set target to ReverseOpenStaticDelegateStub which calls the static function pointer directly
//
IntPtr openStaticFunctionPointer = del.TryGetOpenStaticFunctionPointer();

//
// Allocate unmanaged memory for GCHandle of delegate and function pointer of open static delegate
// We will store this pointer on the context slot of thunk data
//
unsafe
{
ContextData = (IntPtr)NativeMemory.AllocZeroed((nuint)(2 * IntPtr.Size));

// allocate a weak GChandle for the delegate
thunkData->Handle = GCHandle.Alloc(del, GCHandleType.Weak);
ThunkContextData* thunkData = (ThunkContextData*)ContextData;

// if it is an open static delegate get the function pointer
if (del.IsOpenStatic)
thunkData->FunctionPtr = del.GetFunctionPointer(out RuntimeTypeHandle _, out bool _, out bool _);
else
thunkData->FunctionPtr = default;
}
// allocate a weak GChandle for the delegate
thunkData->Handle = GCHandle.Alloc(del, GCHandleType.WeakTrackResurrection);
thunkData->FunctionPtr = openStaticFunctionPointer;
}

IntPtr pTarget = RuntimeInteropData.GetDelegateMarshallingStub(new RuntimeTypeHandle(del.GetMethodTable()), openStaticFunctionPointer != IntPtr.Zero);
Debug.Assert(pTarget != IntPtr.Zero);

RuntimeAugments.SetThunkData(s_thunkPoolHeap, Thunk, ContextData, pTarget);
}

~PInvokeDelegateThunk()
{
// Free the thunk
RuntimeAugments.FreeThunk(s_thunkPoolHeap, Thunk);
unsafe
if (ContextData != IntPtr.Zero)
{
if (ContextData != IntPtr.Zero)
// free the GCHandle
GCHandle handle = ((ThunkContextData*)ContextData)->Handle;
if (handle.IsAllocated)
{
// free the GCHandle
GCHandle handle = ((ThunkContextData*)ContextData)->Handle;
if (handle.IsAllocated)
// If the delegate is still alive, defer finalization.
if (handle.Target != null)
{
handle.Free();
GC.ReRegisterForFinalize(this);
return;
}

// Free the allocated context data memory
NativeMemory.Free((void*)ContextData);
handle.Free();
}

// Free the allocated context data memory
NativeMemory.Free((void*)ContextData);
}

// Free the thunk
if (Thunk != IntPtr.Zero)
{
RuntimeAugments.FreeThunk(s_thunkPoolHeap, Thunk);
}
}
}
Expand All @@ -179,19 +186,7 @@ private static unsafe PInvokeDelegateThunk AllocateThunk(Delegate del)
Debug.Assert(s_thunkPoolHeap != null);
}

var delegateThunk = new PInvokeDelegateThunk(del);

//
// For open static delegates set target to ReverseOpenStaticDelegateStub which calls the static function pointer directly
//
bool openStaticDelegate = del.IsOpenStatic;

IntPtr pTarget = RuntimeInteropData.GetDelegateMarshallingStub(new RuntimeTypeHandle(del.GetMethodTable()), openStaticDelegate);
Debug.Assert(pTarget != IntPtr.Zero);

RuntimeAugments.SetThunkData(s_thunkPoolHeap, delegateThunk.Thunk, delegateThunk.ContextData, pTarget);

return delegateThunk;
return new PInvokeDelegateThunk(del);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,7 @@ public static class DelegateMethodInfoRetriever
{
public static MethodInfo GetDelegateMethodInfo(Delegate del)
{
Delegate[] invokeList = del.GetInvocationList();
del = invokeList[invokeList.Length - 1];
IntPtr originalLdFtnResult = RuntimeAugments.GetDelegateLdFtnResult(del, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver, out bool isInterpreterEntrypoint);

if (isInterpreterEntrypoint)
{
// This is a special kind of delegate where the invoke method is "ObjectArrayThunk". Typically,
// this will be a delegate that points the LINQ Expression interpreter. We could manufacture
// a MethodInfo based on the delegate's Invoke signature, but let's just throw for now.
throw new NotSupportedException(SR.DelegateGetMethodInfo_ObjectArrayDelegate);
}

if (originalLdFtnResult == (IntPtr)0)
return null;
IntPtr originalLdFtnResult = RuntimeAugments.GetDelegateLdFtnResult(del, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver);

QMethodDefinition methodHandle = default(QMethodDefinition);
RuntimeTypeHandle[] genericMethodTypeArgumentHandles = null;
Expand Down Expand Up @@ -79,11 +66,7 @@ public static MethodInfo GetDelegateMethodInfo(Delegate del)
throw new NotSupportedException(SR.Format(SR.DelegateGetMethodInfo_NoDynamic_WithDisplayString, methodDisplayString));
}
}
MethodBase methodBase = ExecutionDomain.GetMethod(typeOfFirstParameterIfInstanceDelegate, methodHandle, genericMethodTypeArgumentHandles);
MethodInfo methodInfo = methodBase as MethodInfo;
if (methodInfo != null)
return methodInfo;
return null; // GetMethod() returned a ConstructorInfo.
return (MethodInfo)ExecutionDomain.GetMethod(typeOfFirstParameterIfInstanceDelegate, methodHandle, genericMethodTypeArgumentHandles);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,6 @@
<data name="DelegateGetMethodInfo_NoInstantiation" xml:space="preserve">
<value>Cannot retrieve a MethodInfo for this delegate because the necessary generic instantiation was not metadata-enabled.</value>
</data>
<data name="DelegateGetMethodInfo_ObjectArrayDelegate" xml:space="preserve">
<value>Cannot retrieve a MethodInfo for this delegate because the delegate target is an interpreted LINQ expression.</value>
</data>
<data name="Arg_InterfaceMapMustNotBeAbstract" xml:space="preserve">
<value>Could not retrieve the mapping of the interface '{0}' on type '{1}' because the type implements the interface abstractly.</value>
</data>
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3285,7 +3285,7 @@ private void getEEInfo(ref CORINFO_EE_INFO pEEInfoOut)

pEEInfoOut.inlinedCallFrameInfo.size = (uint)SizeOfPInvokeTransitionFrame;

pEEInfoOut.offsetOfDelegateInstance = (uint)pointerSize; // Delegate::m_firstParameter
pEEInfoOut.offsetOfDelegateInstance = (uint)pointerSize; // Delegate::_firstParameter
pEEInfoOut.offsetOfDelegateFirstTarget = OffsetOfDelegateFirstTarget;

pEEInfoOut.sizeOfReversePInvokeFrame = (uint)SizeOfReversePInvokeTransitionFrame;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public static MethodIL EmitIL(MethodDesc method)

ILEmitter emit = new ILEmitter();
TypeDesc delegateType = context.GetWellKnownType(WellKnownType.MulticastDelegate).BaseType;
FieldDesc firstParameterField = delegateType.GetKnownField("m_firstParameter");
FieldDesc functionPointerField = delegateType.GetKnownField("m_functionPointer");
FieldDesc firstParameterField = delegateType.GetKnownField("_firstParameter");
FieldDesc functionPointerField = delegateType.GetKnownField("_functionPointer");
ILCodeStream codeStream = emit.NewCodeStream();

codeStream.EmitLdArg(0);
Expand Down
36 changes: 20 additions & 16 deletions src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,31 @@ protected FieldDesc ExtraFunctionPointerOrDataField
{
get
{
return SystemDelegateType.GetKnownField("m_extraFunctionPointerOrData");
return SystemDelegateType.GetKnownField("_extraFunctionPointerOrData");
}
}

protected FieldDesc HelperObjectField
{
get
{
return SystemDelegateType.GetKnownField("m_helperObject");
return SystemDelegateType.GetKnownField("_helperObject");
}
}

protected FieldDesc FirstParameterField
{
get
{
return SystemDelegateType.GetKnownField("m_firstParameter");
return SystemDelegateType.GetKnownField("_firstParameter");
}
}

protected FieldDesc FunctionPointerField
{
get
{
return SystemDelegateType.GetKnownField("m_functionPointer");
return SystemDelegateType.GetKnownField("_functionPointer");
}
}

Expand Down Expand Up @@ -304,7 +304,8 @@ public override MethodIL EmitIL()
ILEmitter emitter = new ILEmitter();
ILCodeStream codeStream = emitter.NewCodeStream();

ArrayType invocationListArrayType = SystemDelegateType.MakeArrayType();
TypeDesc delegateWrapperType = ((MetadataType)SystemDelegateType).GetKnownNestedType("Wrapper");
ArrayType invocationListArrayType = delegateWrapperType.MakeArrayType();

ILLocalVariable delegateArrayLocal = emitter.NewLocal(invocationListArrayType);
ILLocalVariable invocationCountLocal = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));
Expand All @@ -318,21 +319,22 @@ public override MethodIL EmitIL()
}

// Fill in delegateArrayLocal
// Delegate[] delegateArrayLocal = (Delegate[])this.m_helperObject
// Delegate.Wrapper[] delegateArrayLocal = (Delegate.Wrapper[])this._helperObject

// ldarg.0 (this pointer)
// ldfld Delegate.HelperObjectField
// castclass Delegate[]
// ldfld Delegate._helperObject
// castclass Delegate.Wrapper[]
// stloc delegateArrayLocal
codeStream.EmitLdArg(0);
codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(HelperObjectField));
codeStream.Emit(ILOpcode.castclass, emitter.NewToken(invocationListArrayType));
codeStream.EmitStLoc(delegateArrayLocal);

// Fill in invocationCountLocal
// int invocationCountLocal = this.m_extraFunctionPointerOrData
// int invocationCountLocal = this._extraFunctionPointerOrData

// ldarg.0 (this pointer)
// ldfld Delegate.m_extraFunctionPointerOrData
// ldfld Delegate._extraFunctionPointerOrData
// stloc invocationCountLocal
codeStream.EmitLdArg(0);
codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(ExtraFunctionPointerOrDataField));
Expand All @@ -352,25 +354,27 @@ public override MethodIL EmitIL()

// Implement as do/while loop. We only have this stub in play if we're in the multicast situation
// Find the delegate to call
// Delegate = delegateToCallLocal = delegateArrayLocal[iteratorLocal];
// Delegate = delegateToCallLocal = delegateArrayLocal[iteratorLocal].Value;

// ldloc delegateArrayLocal
// ldloc iteratorLocal
// ldelem System.Delegate
// ldelema Delegate.Wrapper
// ldfld Delegate.Wrapper.Value
// stloc delegateToCallLocal
codeStream.EmitLdLoc(delegateArrayLocal);
codeStream.EmitLdLoc(iteratorLocal);
codeStream.Emit(ILOpcode.ldelem, emitter.NewToken(SystemDelegateType));
codeStream.Emit(ILOpcode.ldelema, emitter.NewToken(delegateWrapperType));
codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(delegateWrapperType.GetKnownField("Value")));
codeStream.EmitStLoc(delegateToCallLocal);

// Call the delegate
// returnValueLocal = delegateToCallLocal(...);

// ldloc delegateToCallLocal
// ldfld System.Delegate.m_firstParameter
// ldfld Delegate._firstParameter
// ldarg 1, n
// ldloc delegateToCallLocal
// ldfld System.Delegate.m_functionPointer
// ldfld Delegate._functionPointer
// calli returnValueType thiscall (all the params)
// IF there is a return value
// stloc returnValueLocal
Expand Down Expand Up @@ -501,7 +505,7 @@ public override MethodIL EmitIL()
// args[1] = param1;
// ...
// try {
// ret = ((Func<object[], object>)dlg.m_helperObject)(args);
// ret = ((Func<object[], object>)dlg._helperObject)(args);
// } finally {
// param0 = (T0)args[0]; // only generated for each byref argument
// }
Expand Down
Loading

0 comments on commit cc17166

Please sign in to comment.