P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT
, '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

P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT
, '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

P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT
, '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

P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT
, '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

P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT
, '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

P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT
, '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

P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT
, '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

P/Invokes backed by transient IL - #126509

Merged
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub
Apr 21, 2026
Merged

P/Invokes backed by transient IL#126509
jkoritzinsky merged 21 commits into
dotnet:mainfrom
jkoritzinsky:pinvoke-no-stub

Conversation

@jkoritzinsky

Copy link
Copy Markdown
Member

This PR changes non-varargs P/Invokes to be backed by transient IL instead of by an IL stub.

This change removes 1 DynamicMethodDesc per P/Invoke and removes some interpreter workarounds we had for portable entrypoints (skipping the frame between the P/Invoke and the IL stub for perf) and Swift (ensuring we propagate back the SwiftError register reliably).

This change does not enable Tiered Compilation for P/Invoke methods. They are still considered not tierable and not eligible for ReJIT.

It also reverts #124579 as that work is no longer necessary now that regular P/Invokes are no longer using IL stubs.

jkoritzinskyand others added 7 commits April 2, 2026 21:01
…ause construction
PInvokeStubLinker previously managed try-catch-finally blocks by
manually creating labels, computing offsets, and constructing
ILStubEHClause records. ILCodeStream already provides
BeginTryBlock/EndTryBlock/BeginCatchBlock/EndCatchBlock/
BeginFinallyBlock/EndFinallyBlock APIs that handle this automatically.
Changes:
- Move m_buildingEHClauses and m_finishedEHClauses from ILCodeStream to
ILStubLinker so EH clauses can span multiple code streams (the PInvoke
stub's try-finally begins on the Marshal stream and ends on Cleanup).
- Relax BeginHandler assertion to allow calling before EndTryBlock, since
the finally handler may begin on a different stream than the try body.
- Convert PInvokeStubLinker::Begin to use BeginTryBlock.
- Convert SetCleanupNeeded to use BeginFinallyBlock instead of manually
creating the cleanup finally begin label.
- Convert PInvokeStubLinker::End to use EndTryBlock/EndFinallyBlock
instead of manually creating try-end and finally-end labels.
- Convert EmitExceptionHandler to use EndTryBlock/BeginCatchBlock/
EndCatchBlock instead of manual label management.
- Replace manual EH clause construction (AppendEHClause, PopulateEHSect,
GetCleanupFinallyOffsets) with ILStubLinker::GetNumEHClauses and
WriteEHClauses.
- Add ILStubLinker::GetEHClause for extracting resolved clause info
for logging and ETW.
- Remove PInvokeStubLinker::ClearCode override and manual EH label
fields (m_pCleanupTryBeginLabel, m_pCleanupTryEndLabel,
m_pCleanupFinallyBeginLabel, m_pCleanupFinallyEndLabel).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is no longer necessary as non-vararg forward P/Invokes are now truly non-shared and don't go through the ILStubCache infrastructure at all.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s handling of non-varargs P/Invokes to be implemented via transient IL rather than IL stubs, reducing per-P/Invoke stub overhead and removing related interpreter/workaround paths. It also reworks IL stub EH-clause tracking to support try/handler spans across multiple ILCodeStreams, and updates various call sites to reflect the new P/Invoke execution model.

Changes:

  • Switch non-varargs P/Invokes to transient-IL-backed implementations and adjust prestub/code-prep paths accordingly.
  • Move EH clause tracking from ILCodeStream into ILStubLinker, enabling cross-stream try/finally and try/catch constructs.
  • Update IL stub resolver finalization to accept configurable JIT flags and simplify EH section emission/consumption.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/coreclr/vm/stubgen.hMoves EH clause tracking ownership to ILStubLinker; adds GetEHClause() API.
src/coreclr/vm/stubgen.cppUpdates EH tracking usage across streams; adds resolved EH-clause extraction; clears EH arrays on ClearCode().
src/coreclr/vm/prestub.cppRoutes non-varargs P/Invokes through PrepareInitialCode + explicit P/Invoke target resolution; varargs still use stubs.
src/coreclr/vm/method.cppTreats non-varargs P/Invokes as “may have native code”; explicitly excludes P/Invoke from tiered compilation eligibility; changes x86 stack arg sizing path.
src/coreclr/vm/jitinterface.cppTreats P/Invokes like IL/dynamic methods for getMethodInfo when no IL header is present.
src/coreclr/vm/ilstubresolver.hExtends FinalizeILStub to take CORJIT_FLAGS (defaulting to CORJIT_FLAG_IL_STUB).
src/coreclr/vm/ilstubresolver.cppUses passed-in JIT flags instead of hardcoding IL-stub flags.
src/coreclr/vm/dllimport.hAdds APIs for transient-IL P/Invoke generation and for resolving targets; removes now-unused flag helpers.
src/coreclr/vm/dllimport.cppImplements transient IL generation for P/Invoke; refactors EH handling and stub finalization; updates vararg P/Invoke stub flow.
src/coreclr/inc/CrstTypes.defAdjusts ILStubGen lock ordering metadata.

Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/prestub.cpp Outdated
@jkotas

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/method.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/stubgen.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
@AaronRobinsonMSFT

AaronRobinsonMSFT commented Apr 3, 2026

Copy link
Copy Markdown
Member

Does the change enable P/Invoke stub inlining? We may want to check the impact on JIT throughput.

This is an important thing to check. When the transient IL logic was written we worked to make it "natural" so that from the JIT's perspective it has no clue where the IL comes from. This meant all the UnsafeAccessor functions just got inlined due to their size. Unless something has changed the inlining will happen using the same logic as any other IL JIT logic. This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

This is a long way of saying, we need to seriously think about making all of these inlinable as it could be a very distruptive change. I'm not sure which side I'd come down on now since we have LibraryImport.

@jkotas

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

This could then result in an issue with if checks not blocking missing DLL exceptions for platform checks.

The system is setup to resolve these lazily only once they are actually called, even in the presence of inlining. This should not be a problem (modulo bugs).

Ugh. Why do I keep making this bad assumption. I need a brick to the head or something.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/jitinterface.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/prestub.cpp
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
Comment threadsrc/coreclr/vm/dllimport.cpp Outdated
… paths that resolve P/Invokes for the cases that they handle.
CopilotAI review requested due to automatic review settings April 20, 2026 17:48
Comment threadsrc/coreclr/vm/method.cpp Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/vm/method.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

/ba-g wasm failure is known (addressed by #127177)

@jkoritzinsky
jkoritzinsky merged commit f9b2061 into dotnet:mainApr 21, 2026
109 of 113 checks passed
@jkoritzinsky
jkoritzinsky deleted the pinvoke-no-stub branch April 21, 2026 00:08
BrzVlad added a commit that referenced this pull request May 2, 2026
…ke method (#127660)
We didn't report these transitions when they were triggered from ILStubs
because they already explicitly do this. Following the change of using
transient IL belonging to the actual PInvoke method rather than separate
ILStub methods (which should contain the same logic as the ILStub used
to), we would now report the same event twice. We need therefore to also
ignore the case where we are doing a PInvoke call from an actual PInvoke
method.
Fixes src/tests/profiler/transitions/transitions.csproj which regressed
after #126509 on interpreter.
kotlarmilos added a commit that referenced this pull request May 18, 2026
…es (#128257)
## Description
#125177 implemented Swift calling convention detection in the
interpreter's calli cookie generation. It inspects
m_pMethodBeingCompiled->IsILStub() and walks the IL stub resolver to its
StubTargetMethodDesc. This worked when forward P/Invokes were compiled
as separate IL-stub DynamicMethodDescs.
#126509 changed P/Invoke compilation: the PInvokeMethodDesc itself now
carries the transient IL with the calli to the native target.
MethodDesc::IsILStub() returns true only for DynamicMethodDesc, so
IsILStub() returns false for a transient-IL P/Invoke and pContextMD
stays NULL.
With no context MD, CallStubGenerator::ComputeCallStub cannot enter
either of its Swift detection branches. It falls through to the generic
calling convention switch, leaves m_isSwiftCallConv = false, and:
Never calls RewriteSignatureForSwiftLowering, so the [ref SwiftError,
ref SwiftError] validation never throws InvalidProgramException.
Generates a generic stub that does not preserve x21 across the native
call, so SwiftError is lost on return.
## Fix
In CInterpreterJitInfo::GetCookieForInterpreterCalliSig, also pass the
method being compiled as pContextMD when it is a P/Invoke. The IsPInvoke
branch already in ComputeCallStub then detects the Swift calling
convention via PInvoke::GetCallingConvention_IgnoreErrors.
Fixes#127897Fixes#127898
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 21, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT