Skip to content

JIT: Improve Wasm PEP calls - #134490

Merged
AndyAyersMS merged 5 commits into
dotnet:mainfrom
AndyAyersMS:wasm-unified-pep-calls
Sep 25, 2026
Merged

AndyAyersMS merged 5 commits into
dotnet:mainfrom
AndyAyersMS:wasm-unified-pep-calls

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Summary

  • reuse the managed PEP call target's Wasm local for both the final PEP argument and the indirect-call target
  • remove the explicit PEP local and use one lowering/codegen path for helper and non-helper calls
  • improve the inefficient PEP call sequence reported in [wasm] Performance of GetGCStaticBase could be significantly faster #134450 without attempting to address the other issues discussed there

Example

For System.UInt32.GetHashCode, the null-throw helper path previously stored the PEP to the shadow stack and loaded it twice:

local.get 0
global.get 4326536
i32.load 0 reloc 0x420478
i32.store 0 12
local.get 0
i32.load 0 12
local.get 0
i32.load 0 12
i32.load 0 0
call_indirect 4326584 0

It now tees the PEP into the existing Wasm local and reuses it for the indirect target:

local.get 0
global.get 4326536
i32.load 0 reloc 0x420478
local.tee 3
local.get 3
i32.load 0 0
call_indirect 4326584 0

This method decreases from 116 to 105 bytes.

Code size

Browser CoreLib SuperPMI, 28,985 successful contexts:

Bytes
Baseline 9,973,415
Diff 9,746,072
Change -227,343 (-2.2795%)

16,849 methods improve, 9 regress, and 12,127 are unchanged.

Testing

  • Checked Browser/Wasm native libraries, CoreCLR runtime, cross-JIT, and CoreLib builds
  • full Browser CoreLib SuperPMI replay: 28,985/28,985 contexts succeeded
  • Node execution using a newly built, hash-matched Checked runtime, CoreLib, cross-JIT, and Core_Root:
    • deadoponerrorinfunclet_d
    • deadoponerrorinfunclet_r
    • simpledeadehregion_r

Note

This PR description was generated with GitHub Copilot.

Reuse each managed call target's Wasm local for the PEP argument and indirect
target. This avoids explicit PEP locals that can become shadow-stack traffic.

Addresses part of dotnet#134450; other inefficiencies reported there remain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
Copilot AI lite review requested due to automatic review settings September 23, 2026 00:33
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 23, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
10 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@AndyAyersMS

AndyAyersMS commented Sep 23, 2026 •

Copy link
Copy Markdown
Member Author

@adamperlin PTAL
fyi @dotnet/wasm-contrib

Code size delta is from SPMI, actual browser corelib size should be similar, but I'll verify.

(looks like it is more savings in bytes, lower percent overall given the other stuff in the file)

image

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

Wasm JIT lowering, register allocation, and code generation changes require final human review.

Review effort: Lite
Findings: None

What changed in this PR

Improves WebAssembly PEP call generation by reusing the target local, reducing shadow-stack traffic and code size.

Changes:

  • Updates Wasm register allocation for PEP target temporaries.
  • Uses a contained ABI placeholder during lowering.
  • Reuses the target local for indirect-call emission.
File Description
src/​coreclr/​jit/​regallocwasm.cpp Manages PEP target temporary lifetime.
src/​coreclr/​jit/​lowerwasm.cpp Lowers PEP calls using the original control expression.
src/​coreclr/​jit/​codegenwasm.cpp Emits the optimized indirect-call sequence.

Comment thread src/coreclr/jit/codegenwasm.cpp Outdated
@am11 am11 added the arch-wasm WebAssembly architecture label Sep 23, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

Explain how the control expression supplies both the PEP argument and the
indirect call target.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
Copilot AI review requested due to automatic review settings September 23, 2026 19:07

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

The changes affect JIT lowering, register allocation, and code generation and warrant final human review.

Review effort: Lite
Findings: None

@jakobbotsch jakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the IR here? I'm curious why the straightforward IR representation with a local for the PEP argument and GT_IND on top of that local for the call target doesn't get good codegen.

@AndyAyersMS

Copy link
Copy Markdown
Member Author

This PR is possibly obsolete given that #134555 is changing over to making direct calls to helpers.

What is the IR here? I'm curious why the straightforward IR representation with a local for the PEP argument and GT_IND on top of that local for the call target doesn't get good codegen.

Yeah it is trying to be clever and use wasm locals for this instead. Let me look at a more conventional solution.

@adamperlin

Copy link
Copy Markdown
Contributor

This PR is possibly obsolete given that #134555 is changing over to making direct calls to helpers.

This should still have positive diffs for all non-helper managed calls though I would imagine?

@davidwrighton

Copy link
Copy Markdown
Member

@AndyAyersMS, this PR isn't obsolete. It still improves the case when the module we compile ISN'T also containing System.Private.CoreLib, or when the helper is written in C. I'm going to probably try to address the C helper situation, since those should be able to use a function import, but the modular R2R file istuation would be good to fix.

@AndyAyersMS

AndyAyersMS commented Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Actual problem is that locals introduced post-lower aren't tracked, so aren't considered as register candidates, so for helper calls (added in stack level setter) the PEP local is put on the frame. Seems fixable.

[edit] Though not perhaps fixable without some other similar hackiness. Either I rebuild liveness, adopt a temporary side-car liveness bypass like the one physical promotion uses, teach Wasm RA via some other means that this local is special, or do something like this PR that leverages Wasm multi-use nodes to avoid introducing a local.

@jakobbotsch

Copy link
Copy Markdown
Member

Actual problem is that locals introduced post-lower aren't tracked, so aren't considered as register candidates, so for helper calls (added in stack level setter) the PEP local is put on the frame. Seems fixable.

[edit] Though not perhaps fixable without some other similar hackiness. Either I rebuild liveness, adopt a temporary side-car liveness bypass like the one physical promotion uses, teach Wasm RA via some other means that this local is special, or do something like this PR that leverages Wasm multi-use nodes to avoid introducing a local.

Can we move the existing liveness to run after the stack level setter? I don't think we depend on the current ordering of those.

@AndyAyersMS

Copy link
Copy Markdown
Member Author

car liveness bypass like the one physical promotion uses, teach Wasm RA via some other means that this local is special, or do something like this PR that leverages Wasm multi-use nodes to avoid introducing a local.

Can we move the existing liveness to run after the stack level setter? I don't think we depend on the current ordering of those

Almost, yes. We introduced a dependence recently; I suppose I can just bring back that bit of code.

// Now that we create throw helper blocks after lower,
// we don't need to search for them and set up liveness
// during lower.
assert(!m_compiler->fgRngChkThrowAdded);

@AndyAyersMS

Copy link
Copy Markdown
Member Author

Almost, yes. We introduced a dependence recently; I suppose I can just bring back that bit of code.

That doesn't quite work since we introduce a new local but not new var death flags so liveness and codegen live vars now seemingly diverge. Looking into fixing that now.

Replace the specialized lowering and code generation approach with the late-liveness implementation in the following commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
Copilot AI review requested due to automatic review settings September 24, 2026 21:27
@AndyAyersMS

Copy link
Copy Markdown
Member Author

Ok, got the deferred liveness to work.

Had to recompute live in/last use for throw helpers, verified that it does not lead to new global liveness.

Similar size impact as before (a bit less as the pep locals each get their own wasm local, which can increase encoding size). We could fix this with a "real RA" that assigns different lcl vars to the same wasm local.

Some small diffs on native targets, as zero weight blocks may be in slightly different orders now.

@adamperlin @jakobbotsch ptal

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

Finalization ordering must be corrected or outgoing argument space recomputed.

Review effort: Lite
Findings: None

@AndyAyersMS

Copy link
Copy Markdown
Member Author

Size impact of this version vs previous -- we lost about 12K bytes.

image

@AndyAyersMS

Copy link
Copy Markdown
Member Author

spmi diffs

@jakobbotsch jakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM.

I wonder where the TP regressions are coming from... Is it just the throw helper liveness?

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3c91df4d-d81a-4b77-85ba-1f402109becd
Copilot AI review requested due to automatic review settings September 25, 2026 15:01
@AndyAyersMS

Copy link
Copy Markdown
Member Author

LGTM.

I wonder where the TP regressions are coming from... Is it just the throw helper liveness?

Two things:

  • minopts: unnecessary ref count computation
  • fullopts: unnecessary DFS invalidation/recompute

Both fixed, TP impact should now be minimal

@AndyAyersMS

Copy link
Copy Markdown
Member Author

@jakobbotsch re-approve?
(or @adamperlin)

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

The JIT phase-ordering and liveness changes require final human review.

Review effort: Lite
Findings: None

@AndyAyersMS
AndyAyersMS merged commit d7a058a into dotnet:main Sep 25, 2026
135 of 138 checks passed
@AndyAyersMS

Copy link
Copy Markdown
Member Author

From the updated diffs

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants