-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Conversation
Tagging subscribers to this area: @dotnet/area-system-reflection Issue Details[WIP; testing]
|
src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs
Outdated
Show resolved
Hide resolved
il.Emit(OpCodes.Ldarg_1); | ||
if (method.DeclaringType!.IsValueType) | ||
{ | ||
il.Emit(OpCodes.Unbox, method.DeclaringType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The boxing/unboxing of this
and return value should be moved to the static code too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes per discussion the forcing factor will be supporting the new byref-like type APIs possibly using TypedReference
. That will enable true by-ref returns (with aliasing) and ability to invoke a target value type (and eventually byref-like types) without boxing or making a copy of the target.
src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs
Outdated
Show resolved
Hide resolved
7f4f326
to
fe19afc
Compare
src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs
Show resolved
Hide resolved
29b92bf
to
c26ac61
Compare
src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs
Outdated
Show resolved
Hide resolved
Below is a non-Xunit example. The I believe we could use using System.Reflection;
MethodInfo mi = typeof(MyClass).GetMethod("ThrowException", BindingFlags.Static | BindingFlags.Public);
// Call and display e.StackTrace
try
{
mi.Invoke(null, null);
// or same results with:
// mi.Invoke(null, BindingFlags.DoNotWrapExceptions, null, null, null);
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
Console.WriteLine();
}
// Results before reflection changes (or against 6.0):
/*
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Program.<Main>$(String[] args) in C:\Users\sharter\source\repos\ConsoleApp24\Program.cs:line 14
*/
// Results after reflection changes; consistent but not the same with the "before" results above (since method names\args changed)
/*
at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Program.<Main>$(String[] args) in C:\Users\sharter\source\repos\ConsoleApp24\Program.cs:line 14
*/
// Call and display e.ToString()
try
{
mi.Invoke(null, null);
}
catch (Exception e)
{
Console.WriteLine(e.ToString()); // Same results if we don't wrap in try\catch.
}
// Results before reflection changes (or against 6.0):
/*
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.Exception: Exception of type 'System.Exception' was thrown.
at MyClass.ThrowException() in C:\Users\sharter\source\repos\ConsoleApp24\Program.cs:line 80
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Program.<Main>$(String[] args) in C:\Users\sharter\source\repos\ConsoleApp24\Program.cs:line 43
*/
// Results after reflection changes: NOTE THE 2 EXTRA FRAMES BEFORE THE INNER EXCEPTION
/*
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.Exception: Exception of type 'System.Exception' was thrown.
at MyClass.ThrowException() in C:\Users\sharter\source\repos\ConsoleApp24\Program.cs:line 80
>> at InvokeStub_MyClass.ThrowException(Object, Object, IntPtr*)
>> at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
--- End of inner exception stack trace ---
at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) in C:\git\runtime_reflection7\src\libraries\System.Private.CoreLib\src\System\Reflection\MethodInvoker.cs:line 69
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) in C:\git\runtime_reflection7\src\libraries\System.Private.CoreLib\src\System\Reflection\RuntimeMethodInfo.cs:line 129
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) in C:\git\runtime_reflection7\src\libraries\System.Private.CoreLib\src\System\Reflection\MethodBase.cs:line 54
at Program.<Main>$(String[] args) in C:\Users\sharter\source\repos\ConsoleApp24\Program.cs:line 43
*/
// Note that if the reflection is not using Emit yet (for example on the first call to a MethodInfo) the 2nd frame above will be
// at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
// instead of
// at InvokeStub_MyClass.ThrowException(Object, Object, IntPtr *)
public class MyClass
{
public static void ThrowException()
{
throw new Exception();
}
} |
Failures appear unrelated: |
Improvements seen on arm64 benchmarks: dotnet/perf-autofiling-issues#5284 |
Sorry about that. Submitted revert at #69373 |
For performance, the
Invoke()
method onMethodInfo
,PropertyInfo
,ConstructorInfo
andDynamicMethod
now call to IL-emitted invoke code. A canonical invoke of a method with 4 different types of parameters (int, string, struct, class) is ~3.2x faster including the allocation of theobject[]
to hold the parameters. With a cachedobject[]
, performance increases to ~4.4x. A zero-parameter constructor is ~4.2x faster.This builds upon the recent refactoring work and helps move the code base to supporting byref-like types in the goals.
Not done in this PR:
Notes on the algorithm for determining when to use IL Emit:
RuntimeFeature.IsDynamicCodeCompiled
istrue
. The other alternative is to useIsDynamicCodeSupported
however usingIsDynamicCodeCompiled
is consistent with other areas including DependencyInjection where Mono interpreter scenarios are considered.System.Diagnostics.Tracing.EventSourceAttribute
is invoked through reflection during startup in CoreClr, and no member is called more than once.DynamicMethod
to the native IL emit code (which would need to be done on both Mono and CoreClr). If that switch to native does not occur, trimming support using theIsDynamicCodeCompiled
feature switch will likely be used to remove references toDynamicMethod
.Testing:
System.Reflection.Tests
andSystem.Runtime.Tests
to run under both emit and native invoke.Benchmarks shown below. Notes:
Activator.CreateInstance
that was previously optimized in v6 is not affected by this PR and used to be 6x faster than usingConstructorInfo
. With this PR, however,Activator.CreateInstance
is now just ~1.5x (50%) faster thanConstructorInfo
. The non-zero parameter constructors forActivator.CreateInstance
(which were not optimized in v6) have improved significantly with this PR since they forward toConstructorInfo
and thus are still slower than using the equivalentConstructorInfo
directly.Click for Emit Invoke (fast-path) benchmarks
The same benchmarks as above but using Native Invoke ("slow path"). These are essentially unchanged to moderately faster (up to 1.3x faster):
Click for Native Invoke (slow-path) benchmarks used when emit is not available