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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

Merged
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2
May 23, 2022
Merged

Add IL Emit support for MethodInfo.Invoke() and friends#69575
steveharter merged 2 commits into
dotnet:mainfrom
steveharter:EmitInvoke2

Conversation

@steveharter

@stevehartersteveharter commented May 19, 2022

Copy link
Copy Markdown
Contributor

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:

  • Ran 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.
  • Ran System.Diagnostics.StackTrace.Tests with COMPlus_JitStress=1 plus forcing emit on every invoke. This was also previously failing.
  • Added a new reflection test that previously failed.

The JIT will no longer inline the target method into the generated IL. A future commit will likely switch the implementation from using call and newobj 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 to calli.

Fixes#69251

@stevehartersteveharter added this to the 7.0.0 milestone May 19, 2022
@stevehartersteveharter self-assigned this May 19, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author:steveharter
Assignees:steveharter
Labels:

area-System.Reflection

Milestone:7.0.0

@steveharter
steveharterforce-pushed the EmitInvoke2 branch 2 times, most recently from c6f05f8 to f383835CompareMay 20, 2022 20:28
@steveharter
steveharterforce-pushed the EmitInvoke2 branch 3 times, most recently from 4d52247 to 8899aa0CompareMay 21, 2022 14:57
@steveharter
steveharter marked this pull request as ready for review May 23, 2022 13:20
returnType: typeof(object),
delegateParameters,
restrictedSkipVisibility: true);
typeof(object).Module, // Use system module to identify our DynamicMethods.

Copy link
Copy Markdown
ContributorAuthor

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.

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@steveharter
steveharter merged commit 51df356 into dotnet:mainMay 23, 2022
@steveharter
steveharter deleted the EmitInvoke2 branch May 23, 2022 17:51
Comment on lines +69 to +70
il.Emit(OpCodes.Call, Methods.NextCallReturnAddress()); // For CallStack reasons, don't inline target method.
il.Emit(OpCodes.Pop);

@jakobbotschjakobbotschMay 23, 2022

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

@stevehartersteveharterMay 24, 2022

Copy link
Copy Markdown
ContributorAuthor

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

@ericstjericstj added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label May 24, 2022
@ghostghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label May 24, 2022
@ghost

Copy link
Copy Markdown

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

@ericstj

ericstj commented May 24, 2022

Copy link
Copy Markdown
Member

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.

@AndyAyersMS

AndyAyersMS commented May 27, 2022

Copy link
Copy Markdown
Member

@steveharter

Copy link
Copy Markdown
ContributorAuthor

@sebastienros have you seen any TechEmpower changes from this? Basically scenarios that use reflection to invoke members. Thanks

@sebastienros

sebastienros commented Jun 9, 2022

Copy link
Copy Markdown
Member

@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.

@ericstj

Copy link
Copy Markdown
Member

@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.

@sebastienros

Copy link
Copy Markdown
Member

@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.

@ghostghost locked as resolved and limited conversation to collaborators Jul 9, 2022
@steveharter

steveharter commented Sep 26, 2022

Copy link
Copy Markdown
ContributorAuthor

Removing "breaking change" tags since:

  • The "tailcall" issue was fixed without a breaking change. Originally, the thinking was that a breaking change may be necessary where the breaking change would have been to assume an invoked method may be inlined thus causing Assembly.GetCallingAssembly() to behave differently. This PR preserved the functionality of Assembly.GetCallingAssembly() without a breaking change.
  • Although stack frames may be different now within an invoked method, that does not really raise to the level of a breaking change. See also Consider hiding stack frames when using Invoke #68923 for future changes here where we may try to hide more reflection frames.
  • Exception breaking changes due to the previous refactoring is documented in [Breaking change]: exceptions thrown by reflection Invoke() APIs have changed docs#29199

@stevehartersteveharter removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet breaking-change Issue or PR that represents a breaking API or functional change over a previous release. labels Sep 26, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up for Assembly.GetCallingAssembly() - tests and stack walk

7 participants

@steveharter@ericstj@AndyAyersMS@sebastienros@stephentoub@jkotas@jakobbotsch