Skip to content

Commit

Permalink
Root less stuff for reflectable method signatures (#92994)
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalStrehovsky committed Nov 6, 2023
1 parent 67114e2 commit be45599
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,11 @@ private static Exception CreateChangeTypeException(EETypePtr srcEEType, EETypePt
}

internal static ArgumentException CreateChangeTypeArgumentException(EETypePtr srcEEType, EETypePtr dstEEType, bool destinationIsByRef = false)
=> CreateChangeTypeArgumentException(srcEEType, Type.GetTypeFromHandle(new RuntimeTypeHandle(dstEEType)), destinationIsByRef);

internal static ArgumentException CreateChangeTypeArgumentException(EETypePtr srcEEType, Type dstType, bool destinationIsByRef = false)
{
object? destinationTypeName = Type.GetTypeFromHandle(new RuntimeTypeHandle(dstEEType));
object? destinationTypeName = dstType;
if (destinationIsByRef)
destinationTypeName += "&";
return new ArgumentException(SR.Format(SR.Arg_ObjObjEx, Type.GetTypeFromHandle(new RuntimeTypeHandle(srcEEType)), destinationTypeName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,36 +72,37 @@ public DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk)
{
Transform transform = default;

Type argumentType = parameters[i].ParameterType;
var argumentType = (RuntimeType)parameters[i].ParameterType;
if (argumentType.IsByRef)
{
_needsCopyBack = true;
transform |= Transform.ByRef;
argumentType = argumentType.GetElementType()!;
argumentType = (RuntimeType)argumentType.GetElementType()!;
}
Debug.Assert(!argumentType.IsByRef);

EETypePtr eeArgumentType = argumentType.TypeHandle.ToEETypePtr();

if (eeArgumentType.IsValueType)
// This can return a null MethodTable for reference types.
// The compiler makes sure it returns a non-null MT for everything else.
EETypePtr eeArgumentType = argumentType.ToEETypePtrMayBeNull();
if (argumentType.IsValueType)
{
Debug.Assert(argumentType.IsValueType);
Debug.Assert(eeArgumentType.IsValueType);

if (eeArgumentType.IsByRefLike)
_argumentCount = ArgumentCount_NotSupported_ByRefLike;

if (eeArgumentType.IsNullable)
transform |= Transform.Nullable;
}
else if (eeArgumentType.IsPointer)
else if (argumentType.IsPointer)
{
Debug.Assert(argumentType.IsPointer);
Debug.Assert(eeArgumentType.IsPointer);

transform |= Transform.Pointer;
}
else if (eeArgumentType.IsFunctionPointer)
else if (argumentType.IsFunctionPointer)
{
Debug.Assert(argumentType.IsFunctionPointer);
Debug.Assert(eeArgumentType.IsFunctionPointer);

transform |= Transform.FunctionPointer;
}
Expand All @@ -119,19 +120,18 @@ public DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk)
{
Transform transform = default;

Type returnType = methodInfo.ReturnType;
var returnType = (RuntimeType)methodInfo.ReturnType;
if (returnType.IsByRef)
{
transform |= Transform.ByRef;
returnType = returnType.GetElementType()!;
returnType = (RuntimeType)returnType.GetElementType()!;
}
Debug.Assert(!returnType.IsByRef);

EETypePtr eeReturnType = returnType.TypeHandle.ToEETypePtr();

if (eeReturnType.IsValueType)
EETypePtr eeReturnType = returnType.ToEETypePtrMayBeNull();
if (returnType.IsValueType)
{
Debug.Assert(returnType.IsValueType);
Debug.Assert(eeReturnType.IsValueType);

if (returnType != typeof(void))
{
Expand All @@ -150,17 +150,17 @@ public DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk)
_argumentCount = ArgumentCount_NotSupported; // ByRef to void return
}
}
else if (eeReturnType.IsPointer)
else if (returnType.IsPointer)
{
Debug.Assert(returnType.IsPointer);
Debug.Assert(eeReturnType.IsPointer);

transform |= Transform.Pointer;
if ((transform & Transform.ByRef) == 0)
transform |= Transform.AllocateReturnBox;
}
else if (eeReturnType.IsFunctionPointer)
else if (returnType.IsFunctionPointer)
{
Debug.Assert(returnType.IsFunctionPointer);
Debug.Assert(eeReturnType.IsFunctionPointer);

transform |= Transform.FunctionPointer;
if ((transform & Transform.ByRef) == 0)
Expand Down Expand Up @@ -597,6 +597,12 @@ private unsafe ref byte InvokeDirectWithFewArguments(
return defaultValue;
}

private void ThrowForNeverValidNonNullArgument(EETypePtr srcEEType, int index)
{
Debug.Assert(index != 0 || _isStatic);
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, Method.GetParametersAsSpan()[index - (_isStatic ? 0 : 1)].ParameterType, destinationIsByRef: false);
}

private unsafe void CheckArguments(
Span<object?> copyOfParameters,
void* byrefParameters,
Expand Down Expand Up @@ -636,16 +642,25 @@ private unsafe void CheckArguments(
EETypePtr srcEEType = arg.GetEETypePtr();
EETypePtr dstEEType = argumentInfo.Type;

if (!(srcEEType.RawValue == dstEEType.RawValue ||
RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType.IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
&& castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false))))
if (srcEEType.RawValue != dstEEType.RawValue)
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);
// Destination type can be null if we don't have a MethodTable for this type. This means one cannot
// possibly pass a valid non-null object instance here.
if (dstEEType.IsNull)
{
ThrowForNeverValidNonNullArgument(srcEEType, i);
}

arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle);
if (!(RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType.IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
&& castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false))))
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);

arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle);
}
}

if ((argumentInfo.Transform & Transform.Reference) == 0)
Expand Down Expand Up @@ -704,16 +719,25 @@ private unsafe void CheckArguments(
EETypePtr srcEEType = arg.GetEETypePtr();
EETypePtr dstEEType = argumentInfo.Type;

if (!(srcEEType.RawValue == dstEEType.RawValue ||
RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType.IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
&& castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false))))
if (srcEEType.RawValue != dstEEType.RawValue)
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);
// Destination type can be null if we don't have a MethodTable for this type. This means one cannot
// possibly pass a valid non-null object instance here.
if (dstEEType.IsNull)
{
ThrowForNeverValidNonNullArgument(srcEEType, i);
}

arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle: null);
if (!(RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) ||
(dstEEType.IsInterface && arg is System.Runtime.InteropServices.IDynamicInterfaceCastable castable
&& castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false))))
{
// ByRefs have to be exact match
if ((argumentInfo.Transform & Transform.ByRef) != 0)
throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true);

arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle: null);
}
}

if ((argumentInfo.Transform & Transform.Reference) == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ internal static void AddSignatureDependency(ref DependencyList dependencies, Nod
if (type.IsPrimitive || type.IsVoid)
return;

// Reflection doesn't need the ability to generate MethodTables out of thin air for reference types.
// Skip generating the dependencies.
if (type.IsGCPointer)
return;

TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
if (canonType.IsCanonicalSubtype(CanonicalFormKind.Any))
GenericTypesTemplateMap.GetTemplateTypeDependencies(ref dependencies, factory, canonType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static int Run()
{
SanityTest.Run();
TestInstanceMethodOptimization.Run();
TestReflectionInvokeSignatures.Run();
TestAbstractTypeNeverDerivedVirtualsOptimization.Run();
TestAbstractNeverDerivedWithDevirtualizedCall.Run();
TestAbstractDerivedByUnrelatedTypeWithDevirtualizedCall.Run();
Expand Down Expand Up @@ -75,6 +76,32 @@ public static void Run()
}
}

class TestReflectionInvokeSignatures
{
public class Never1 { }

public static void Invoke1(Never1 inst) { }

public struct Allocated1 { }

public static void Invoke2(out Allocated1 inst) { inst = default; }

public static void Run()
{
{
MethodInfo mi = typeof(TestReflectionInvokeSignatures).GetMethod(nameof(Invoke1));
mi.Invoke(null, new object[1]);
ThrowIfPresentWithUsableMethodTable(typeof(TestReflectionInvokeSignatures), nameof(Never1));
}

{
MethodInfo mi = typeof(TestReflectionInvokeSignatures).GetMethod(nameof(Invoke2));
mi.Invoke(null, new object[1]);
ThrowIfNotPresent(typeof(TestReflectionInvokeSignatures), nameof(Allocated1));
}
}
}

class TestAbstractTypeNeverDerivedVirtualsOptimization
{
class UnreferencedType1
Expand Down

0 comments on commit be45599

Please sign in to comment.