-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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 #69575
Conversation
Tagging subscribers to this area: @dotnet/area-system-reflection Issue DetailsBrings back the original commit from #67917 plus a new commit to prevent a missing stack frame that blocked SDK integration thus causing the original commit to be reverted.
|
c6f05f8
to
f383835
Compare
4d52247
to
8899aa0
Compare
@@ -29,7 +29,8 @@ public static unsafe InvokeFunc CreateInvokeDelegate(MethodBase method) | |||
InvokeStubPrefix + declaringTypeName + method.Name, | |||
returnType: typeof(object), | |||
delegateParameters, | |||
restrictedSkipVisibility: true); | |||
typeof(object).Module, // Use system module to identify our DynamicMethods. |
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.
This helps address #69251 by assigning the system module. We could also create our own module with a special name and compare that, which would reduce all chances of naming collision but would require a new module\alloc.
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.
Thanks
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method. | ||
il.Emit(OpCodes.Pop); |
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.
Out of curiosity, did you check codegen/your benchmarks when this is used? Just to verify that there is no significant perf impact by using this (beyond what is expected by not inlining the target call).
FWIW, in the current JIT I believe this will actually end up suppressing inlining for the remainder of the IL it sees. That shouldn't be too bad for common cases although I can see that it may have some impact on pointer/by-ref returns below.
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.
FWIW, in the current JIT I believe this will actually end up suppressing inlining for the remainder of the IL it sees
For my edification, what is "this" in the above statement?
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.
System.StubHelpers.StubHelpers.NextCallReturnAddress()
is an intrinsic used to implement tailcalls. It has the side effect of guaranteeing that the next call-producing IL instruction will not be inlined by the JIT, so I suggested to @steveharter to use it in #69154.
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.
Cool, thanks.
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.
did you check codegen/your benchmarks when this is used
There was perhaps a slight regression, but still within the margin of error. I'm not too concerned since we'd want to switch to use calli
anyway and remove the call to the intrisic.
Just doing a single run and picking the most canonical benchmark:
previous
| Method | Job | Toolchain | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen 0 | Allocated | Alloc Ratio |
|----------------------------------------------------------------------------- |----------- |----------------------- |-----------:|-----------:|-----------:|-----------:|-----------:|-----------:|------:|--------:|-------:|----------:|------------:|
| StaticMethod4_int_string_struct_class | Job-AEFDZT | \main\corerun.exe | 200.804 ns | 3.3102 ns | 2.7642 ns | 200.939 ns | 194.044 ns | 205.150 ns | 3.22 | 0.07 | 0.0146 | 160 B | 1.00 |
| StaticMethod4_int_string_struct_class | Job-VAFQFP | \newinvoke\corerun.exe | 62.374 ns | 0.8406 ns | 0.7452 ns | 62.538 ns | 61.110 ns | 63.808 ns | 1.00 | 0.00 | 0.0151 | 160 B | 1.00
current
| Method | Job | Toolchain | Mean | Error | StdDev | Median | Min | Max | Ratio | RatioSD | Gen 0 | Allocated | Alloc Ratio |
|----------------------------------------------------------------------------- |----------- |------------------------ |-----------:|----------:|----------:|-----------:|-----------:|-----------:|------:|--------:|-------:|----------:|------------:|
| StaticMethod4_int_string_struct_class | Job-WQJSNV | \main\corerun.exe | 200.456 ns | 1.4665 ns | 1.3717 ns | 200.110 ns | 197.585 ns | 202.696 ns | 3.12 | 0.03 | 0.0145 | 160 B | 1.00 |
| StaticMethod4_int_string_struct_class | Job-IAUHXD | \newinvoke3\corerun.exe | 64.245 ns | 0.4365 ns | 0.3869 ns | 64.281 ns | 63.756 ns | 65.193 ns | 1.00 | 0.00 | 0.0151 | 160 B |
it went from a ratio of 3.22
to 3.12
Added When you commit this breaking change:
Tagging @dotnet/compat for awareness of the breaking change. |
Let's consider this "breaking" as a functional breaking change. That way we can get some docs that let folks know about the types of behavior changes they might see as a result of this change. -- Rereading your mitigations above, feel free to remove the breaking change tag if you feel that this is less breaking in its current form. |
Improvements seen on ARM64 perf.
Also seems like the auto-filer may be repeatedly filing for the same issue. cc @DrewScoggins |
@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks |
@steveharter Nothing visible on the charts or caught by the bot. In the future ping me when the PR is still open and if you have a local build I can show you how to check if there is an impact. |
@sebastienros do you have any other ASP.NET benchmarks that we could check? I would hope that tech-empower doesn't have reflection invoke on the hot path but perhaps other ASP.NET scenarios do. |
@ericstj I looked at MVC scenarios actually because this is where we should see reflection the most. And nothing special shows up around the date it was merged. There is an MVC page in the dashboard with different scenarios all using MVC. |
Removing "breaking change" tags since:
|
Brings back the original commit from #67917 plus a new commit to prevent a missing stack frame that blocked SDK integration thus causing the original commit to be reverted.
Testing performed with these changes:
Microsoft.NET.Sdk.Razor.Tests
in the SDK's integration tests locally which were previously failing in some cases due to chained constructors calling Assembly.GetCallingAssembly` to find the calling test class assembly.System.Diagnostics.StackTrace.Tests
withCOMPlus_JitStress=1
plus forcing emit on every invoke. This was also previously failing.The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using
call
andnewobj
opcodes to using function pointers (calli
opcode plus a call to allocate for constructors). Doing that and passing the method pointer into the generated method will also prevent the JIT from inlining the target method, so in effect this PR prevents a temporary breaking change until we switch tocalli
.Fixes #69251