Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IL Emit support for MethodInfo.Invoke() and friends #67917

Merged
merged 23 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d4f6b51
Add IL Emit support for MethodInfo.Invoke()
steveharter Apr 11, 2022
023299c
Test updates
steveharter Apr 13, 2022
19959c0
Create true Nullable<T>s to simplify native and IL
steveharter Apr 19, 2022
40eb391
Update Emit code for new Nullability; still running non-emit for tests
steveharter Apr 19, 2022
a1f7a5c
Enable IL for testing; improve perf for ByRef param validation
steveharter Apr 20, 2022
50cd45b
Merge branch 'main' of https://github.com/steveharter/runtime into Em…
steveharter Apr 21, 2022
ba141ce
Fixup merge
steveharter Apr 21, 2022
79571d9
Ensure a copy is made for byref value types
steveharter Apr 22, 2022
c2ae6e4
Remove temps in IL
steveharter Apr 24, 2022
97dc203
Remove hard check for Type.GetType()
steveharter Apr 24, 2022
69d467d
Active issue some tests; misc other
steveharter Apr 25, 2022
259c4e0
Remove IL-only test hack; ready for review
steveharter Apr 27, 2022
9dab8f7
Add emit to DynamicMethod; other misc feedback
steveharter Apr 28, 2022
e7878ff
Fix Mono compile issue
steveharter Apr 28, 2022
13f913a
Fix shuffle thunk; other misc non-functional
steveharter Apr 29, 2022
1516fbf
Fix possible null method.DeclaringType; binding test failures with Ch…
steveharter May 2, 2022
54e0a77
Fix DynamicMethod.Invoke when using Emit-based invoke
steveharter May 3, 2022
89a0483
Disable some JIT Stress tests on arm32 linux
steveharter May 3, 2022
9e50960
renaming; native perf; other feedback
steveharter May 5, 2022
53a4167
Nit feedback
steveharter May 5, 2022
8efba3d
For perf, avoid calling ReboxFromNullable when not necessary
steveharter May 6, 2022
55a4f95
Merge branch 'main' into EmitInvoke
steveharter May 6, 2022
c7bee67
For perf, avoid calling ReboxFromNullable when not necessary part 2
steveharter May 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<EnableDefaultItems>false</EnableDefaultItems>
Expand Down Expand Up @@ -155,12 +155,12 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\AssemblyName.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Associates.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\ConstructorInfo.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\ConstructorInvoker.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\AssemblyBuilder.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\ConstructorBuilder.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\CustomAttributeBuilder.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\DynamicILGenerator.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\DynamicMethod.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\DynamicMethodInvoker.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\EnumBuilder.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\EventBuilder.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\FieldBuilder.cs" />
Expand All @@ -187,6 +187,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\AssemblyExtensions.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\MetadataUpdater.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MethodBase.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MethodInvoker.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RtFieldInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeAssembly.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeConstructorInfo.CoreCLR.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

namespace System.Reflection
{
internal partial class ConstructorInvoker
{
public InvocationFlags _invocationFlags;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe object? InterpretedInvoke(object? obj, IntPtr* arguments)
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _method.Signature, isConstructor: obj is null)!;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed class DynamicMethod : MethodInfo
private RuntimeModule m_module = null!;
internal bool m_skipVisibility;
internal RuntimeType? m_typeOwner; // can be null
private DynamicMethodInvoker? _invoker;
private MethodInvoker? _invoker;
private Signature? _signature;

// We want the creator of the DynamicMethod to control who has access to the
Expand Down Expand Up @@ -434,13 +434,12 @@ internal RuntimeMethodHandle GetMethodDescriptor()

public override bool IsSecurityTransparent => false;

private DynamicMethodInvoker Invoker
private MethodInvoker Invoker
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
_invoker ??= new DynamicMethodInvoker(this);

_invoker ??= new MethodInvoker(this, Signature);
return _invoker;
}
}
Expand Down Expand Up @@ -490,7 +489,7 @@ Signature LazyCreateSignature()
{
if (argCount == 0)
{
retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr);
retValue = Invoker.InlinedInvoke(obj, args: default, invokeAttr);
}
else if (argCount > MaxStackAllocArgCount)
{
Expand All @@ -501,8 +500,8 @@ Signature LazyCreateSignature()
{
Debug.Assert(parameters != null);
StackAllocedArguments argStorage = default;
Span<object?> copyOfParameters = new Span<object?>(ref argStorage._arg0, argCount);
Span<bool> shouldCopyBackParameters = new Span<bool>(ref argStorage._copyBack0, argCount);
Span<object?> copyOfParameters = new(ref argStorage._arg0, argCount);
Span<ParameterCopyBackAction> shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount);

StackAllocatedByRefs byrefStorage = default;
IntPtr* pByRefStorage = (IntPtr*)&byrefStorage;
Expand All @@ -517,14 +516,25 @@ Signature LazyCreateSignature()
culture,
invokeAttr);

retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, invokeAttr);
retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr);

// Copy modified values out. This should be done only with ByRef or Type.Missing parameters.
for (int i = 0; i < argCount; i++)
{
if (shouldCopyBackParameters[i])
ParameterCopyBackAction action = shouldCopyBackParameters[i];
if (action != ParameterCopyBackAction.None)
{
parameters[i] = copyOfParameters[i];
if (action == ParameterCopyBackAction.Copy)
{
parameters[i] = copyOfParameters[i];
}
else
{
Debug.Assert(action == ParameterCopyBackAction.CopyNullable);
Debug.Assert(copyOfParameters[i] != null);
Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT);
parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]);
}
}
}
}
Expand All @@ -546,15 +556,15 @@ Signature LazyCreateSignature()
CultureInfo? culture)
{
object[] objHolder = new object[argCount];
Span<object?> copyOfParameters = new Span<object?>(objHolder, 0, argCount);
Span<object?> copyOfParameters = new(objHolder, 0, argCount);

// We don't check a max stack size since we are invoking a method which
// naturally requires a stack size that is dependent on the arg count\size.
IntPtr* pByRefStorage = stackalloc IntPtr[argCount];
Buffer.ZeroMemory((byte*)pByRefStorage, (uint)(argCount * sizeof(IntPtr)));

bool* boolHolder = stackalloc bool[argCount];
Span<bool> shouldCopyBackParameters = new Span<bool>(boolHolder, argCount);
ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount];
Span<ParameterCopyBackAction> shouldCopyBackParameters = new(copyBackActions, argCount);

GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true);

Expand All @@ -572,7 +582,7 @@ Signature LazyCreateSignature()
culture,
invokeAttr);

retValue = mi.Invoker.InvokeUnsafe(obj, pByRefStorage, invokeAttr);
retValue = mi.Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr);
}
finally
{
Expand All @@ -582,36 +592,26 @@ Signature LazyCreateSignature()
// Copy modified values out. This should be done only with ByRef or Type.Missing parameters.
for (int i = 0; i < argCount; i++)
{
if (shouldCopyBackParameters[i])
ParameterCopyBackAction action = shouldCopyBackParameters[i];
if (action != ParameterCopyBackAction.None)
{
parameters[i] = copyOfParameters[i];
if (action == ParameterCopyBackAction.Copy)
{
parameters[i] = copyOfParameters[i];
}
else
{
Debug.Assert(action == ParameterCopyBackAction.CopyNullable);
Debug.Assert(copyOfParameters[i] != null);
Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT);
parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]);
}
}
}

return retValue;
}

[DebuggerHidden]
[DebuggerStepThrough]
internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr)
{
if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0)
{
try
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false);
}
catch (Exception e)
{
throw new TargetInvocationException(e);
}
}
else
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false);
}
}

public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
return m_dynMethod.GetCustomAttributes(attributeType, inherit);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

namespace System.Reflection
{
internal partial class MethodInvoker
{
private readonly Signature _signature;
internal InvocationFlags _invocationFlags;

public MethodInvoker(MethodBase method, Signature signature)
{
_method = method;
_signature = signature;

#if USE_NATIVE_INVOKE
// Always use the native invoke; useful for testing.
_strategyDetermined = true;
#elif USE_EMIT_INVOKE
// Always use emit invoke (if IsDynamicCodeCompiled == true); useful for testing.
_invoked = true;
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe object? InterpretedInvoke(object? obj, IntPtr* arguments)
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _signature, isConstructor: false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt

CheckConsistency(obj);

bool _ref = false;
ParameterCopyBackAction _ref = default;
RuntimeType fieldType = (RuntimeType)FieldType;
if (value is null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,27 +95,6 @@ Signature LazyCreateSignature()

internal BindingFlags BindingFlags => m_bindingFlags;


[DebuggerStepThrough]
[DebuggerHidden]
internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span<object?> argsForTemporaryMonoSupport, BindingFlags invokeAttr)
{
if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0)
{
try
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, Signature, isConstructor: obj is null)!;
}
catch (Exception ex)
{
throw new TargetInvocationException(ex);
}
}
else
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, Signature, isConstructor: obj is null)!;
}
}
#endregion

#region Object Overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private MethodInvoker Invoker
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
m_invoker ??= new MethodInvoker(this);
m_invoker ??= new MethodInvoker(this, Signature);
return m_invoker;
}
}
Expand Down Expand Up @@ -351,7 +351,7 @@ public override MethodImplAttributes GetMethodImplementationFlags()
StackAllocedArguments argStorage = default;
Span<object?> copyOfParameters = new(ref argStorage._arg0, 1);
ReadOnlySpan<object?> parameters = new(in parameter);
Span<bool> shouldCopyBackParameters = new(ref argStorage._copyBack0, 1);
Span<ParameterCopyBackAction> shouldCopyBackParameters = new(ref argStorage._copyBack0, 1);

StackAllocatedByRefs byrefStorage = default;
IntPtr* pByRefStorage = (IntPtr*)&byrefStorage;
Expand All @@ -366,33 +366,16 @@ public override MethodImplAttributes GetMethodImplementationFlags()
culture,
invokeAttr);

retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr);
#if MONO // Temporary until Mono is updated.
retValue = Invoker.InlinedInvoke(obj, copyOfParameters, invokeAttr);
#else
retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr);
#endif
}

return retValue;
}

[DebuggerHidden]
[DebuggerStepThrough]
internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, Span<object?> argsForTemporaryMonoSupport, BindingFlags invokeAttr)
{
if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0)
{
try
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false);
}
catch (Exception e)
{
throw new TargetInvocationException(e);
}
}
else
{
return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false);
}
}

#endregion

#region MethodInfo Overrides
Expand Down
19 changes: 19 additions & 0 deletions src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ internal static bool IsByRef(RuntimeType type)
return corElemType == CorElementType.ELEMENT_TYPE_BYREF;
}

internal static bool TryGetByRefElementType(RuntimeType type, [NotNullWhen(true)] out RuntimeType? elementType)
{
CorElementType corElemType = GetCorElementType(type);
if (corElemType == CorElementType.ELEMENT_TYPE_BYREF)
{
elementType = GetElementType(type);
return true;
}

elementType = null;
return false;
}

internal static bool IsPointer(RuntimeType type)
{
CorElementType corElemType = GetCorElementType(type);
Expand Down Expand Up @@ -981,6 +994,12 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method)
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object? ReboxFromNullable(object? src);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object ReboxToNullable(object? src, RuntimeType destNullableType);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetMethodInstantiation")]
private static partial void GetMethodInstantiation(RuntimeMethodHandleInternal method, ObjectHandleOnStack types, Interop.BOOL fAsRuntimeTypeArray);

Expand Down
Loading