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

Reflection Invoke refactoring to support future byref-like and IL emit features #66357

Merged
merged 29 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7ae039b
Refactoring and perf of reflection Invoke
steveharter Feb 2, 2022
840c463
Stubs for register/unregister GC reporting
AaronRobinsonMSFT Mar 7, 2022
f9c0cac
Uptake GC Registration for core (not mono)
steveharter Mar 12, 2022
2587e3f
Remove scaffolding; remove Assert
steveharter Mar 14, 2022
fc0c044
Add GCFrameRegistration API to Mono
AaronRobinsonMSFT Mar 14, 2022
7605f5d
Refactor invoker classes to move code to runtime to avoid stackwalk i…
steveharter Mar 14, 2022
54c7970
Misc changes for review prep
steveharter Mar 15, 2022
99d750d
Initial support for passing byrefs to raw value types
steveharter Mar 24, 2022
3334686
Continued by-ref support; still issues with nullables
steveharter Mar 25, 2022
7891c3a
Address nullable issues and other clean up
steveharter Mar 27, 2022
3635c16
Fix merge issue
steveharter Mar 27, 2022
408fea3
Misc mono and caching
steveharter Mar 28, 2022
1ab5f60
Address some CI issues
steveharter Mar 28, 2022
8e9e2a3
Disable new Nullable tests on Mono
steveharter Mar 28, 2022
65ccbee
Assume no padding on arg values
steveharter Mar 29, 2022
8c18cb9
Review feedback
steveharter Mar 29, 2022
791bdb2
Align\pad work; remove TypedReference since it can't be boxed
steveharter Mar 29, 2022
b27db5c
Misc micro optimizations
steveharter Mar 31, 2022
61f9deb
Misc naming
steveharter Mar 31, 2022
be216b5
Remove support for ByRefLike + null; misc feedback
steveharter Apr 1, 2022
9ade961
Remove support for ByRefLike + null on mono
steveharter Apr 1, 2022
bfd1062
Wrap all exceptions thrown from ::InvokeMethod; misc perf refactor
steveharter Apr 1, 2022
6cb7837
Mono tests; cont'd perf refactoring
steveharter Apr 1, 2022
1f81dc6
Additional validation checks
steveharter Apr 2, 2022
b8e3967
Merge branch 'main' of https://github.com/steveharter/runtime into Fa…
steveharter Apr 2, 2022
c3a8c14
Fix mono error; fix debug-build stack-walk issue
steveharter Apr 3, 2022
59ef825
Don't wrap EntryPointNotFoundException; misc naming
steveharter Apr 3, 2022
b667080
Revert previous EntryPointNotFoundException; modify test
steveharter Apr 3, 2022
6cd29a3
Non-functional nits
steveharter Apr 4, 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
Expand Up @@ -160,6 +160,7 @@
<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 Down Expand Up @@ -315,7 +316,7 @@

<Import Project="CreateRuntimeRootILLinkDescriptorFile.targets" />

<Target Name="CreateRuntimeRootIlLinkDescFile" BeforeTargets="CoreCompile" DependsOnTargets="_CreateILLinkRuntimeRootDescriptorFile"/>
<Target Name="CreateRuntimeRootIlLinkDescFile" BeforeTargets="CoreCompile" DependsOnTargets="_CreateILLinkRuntimeRootDescriptorFile" />

<Import Project="$(RepositoryEngineeringDir)codeOptimization.targets" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Runtime.Loader;
using System.Text;
using System.Threading;
using static System.Runtime.CompilerServices.RuntimeHelpers;

namespace System.Reflection.Emit
{
Expand All @@ -22,6 +23,8 @@ 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 Signature? _signature;

// We want the creator of the DynamicMethod to control who has access to the
// DynamicMethod (just like we do for delegates). However, a user can get to
Expand Down Expand Up @@ -417,6 +420,37 @@ internal RuntimeMethodHandle GetMethodDescriptor()

public override bool IsSecurityTransparent => false;

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

return _invoker;
}
}

internal Signature Signature
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
[MethodImpl(MethodImplOptions.NoInlining)] // move lazy sig generation out of the hot path
Signature LazyCreateSignature()
{
Debug.Assert(m_methodHandle != null);
Debug.Assert(m_parameterTypes != null);

Signature newSig = new Signature(m_methodHandle, m_parameterTypes, m_returnType, CallingConvention);
Volatile.Write(ref _signature, newSig);
return newSig;
}

return _signature ?? LazyCreateSignature();
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
}
}

public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture)
{
if ((CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs)
Expand All @@ -431,38 +465,139 @@ internal RuntimeMethodHandle GetMethodDescriptor()
_ = GetMethodDescriptor();
// ignore obj since it's a static method

// create a signature object
Signature sig = new Signature(
this.m_methodHandle!, m_parameterTypes, m_returnType, CallingConvention);


// verify arguments
int formalCount = sig.Arguments.Length;
int actualCount = (parameters != null) ? parameters.Length : 0;
if (formalCount != actualCount)
int argCount = (parameters != null) ? parameters.Length : 0;
if (Signature.Arguments.Length != argCount)
throw new TargetParameterCountException(SR.Arg_ParmCnt);

// if we are here we passed all the previous checks. Time to look at the arguments
bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
object? retValue;

StackAllocedArguments stackArgs = default;
Span<object?> arguments = default;
if (actualCount != 0)
unsafe
{
arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig.Arguments);
if (argCount == 0)
{
retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr);
}
else if (argCount > MaxStackAllocArgCount)
{
Debug.Assert(parameters != null);
retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture);
}
else
{
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);

StackAllocatedByRefs byrefStorage = default;
IntPtr* pByRefStorage = (IntPtr*)&byrefStorage;

CheckArguments(
copyOfParameters,
pByRefStorage,
shouldCopyBackParameters,
parameters,
Signature.Arguments,
binder,
culture,
invokeAttr);

retValue = Invoker.InvokeUnsafe(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])
{
parameters[i] = copyOfParameters[i];
}
}
}
}

object? retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, false, wrapExceptions);
GC.KeepAlive(this);
return retValue;
}

// Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects.
// This is a separate method to support better performance for the faster paths.
private static unsafe object? InvokeWithManyArguments(
DynamicMethod mi,
int argCount,
object? obj,
BindingFlags invokeAttr,
Binder? binder,
object?[] parameters,
CultureInfo? culture)
{
object[] objHolder = new object[argCount];
Span<object?> copyOfParameters = new Span<object?>(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)));

// copy out. This should be made only if ByRef are present.
// n.b. cannot use Span<T>.CopyTo, as parameters.GetType() might not actually be typeof(object[])
for (int index = 0; index < arguments.Length; index++)
parameters![index] = arguments[index];
bool* boolHolder = stackalloc bool[argCount];
Span<bool> shouldCopyBackParameters = new Span<bool>(boolHolder, argCount);

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

object? retValue;
try
{
RegisterForGCReporting(&reg);
mi.CheckArguments(
copyOfParameters,
pByRefStorage,
shouldCopyBackParameters,
parameters,
mi.Signature.Arguments,
binder,
culture,
invokeAttr);

retValue = mi.Invoker.InvokeUnsafe(obj, pByRefStorage, invokeAttr);
}
finally
{
UnregisterForGCReporting(&reg);
}

// 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])
{
parameters[i] = copyOfParameters[i];
}
}

GC.KeepAlive(this);
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System.Reflection.Emit
{
internal sealed partial class DynamicMethodInvoker
{
private readonly DynamicMethod _dynamicMethod;

public DynamicMethodInvoker(DynamicMethod dynamicMethod)
{
_dynamicMethod = dynamicMethod;
}

public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr)
{
// Todo: add strategy for calling IL Emit-based version
return _dynamicMethod.InvokeNonEmitUnsafe(obj, args, invokeAttr);
}
}
}
Loading