Skip to content

[wasm][JIT] Don't assume the SP arg of a fast tail call is a GT_PHYSREG - #135293

Draft
lewing wants to merge 3 commits into
dotnet:mainfrom
lewing:lewing-wasm-jit-sp-arg-no-spill
Draft

lewing wants to merge 3 commits into
dotnet:mainfrom
lewing:lewing-wasm-jit-sp-arg-no-spill

Conversation

@lewing

@lewing lewing commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

For a wasm fast tail call, WasmRegAlloc::CollectReferencesForCall wraps the shadow stack pointer (SP) argument in ADD(SP, FRAME_SIZE) so the callee sees the incoming SP. It asserted that the SP arg is a GT_PHYSREG, but an operand can be evaluated into a temp for several reasons. For example, a long sb.Append(..).Append(..)...ToString() chain makes gtMayHaveStoreInterference give up after 8 stores, so arg morphing spills the SP arg to a temp, and the assert fired (regallocwasm.cpp:589).

The temp holds the same post-prolog SP value, so wrapping it is equally correct. This change accepts GT_LCL_VAR as well as GT_PHYSREG and only checks gtSrcReg for a GT_PHYSREG.

Validation: a checked wasm crossgen2 without the explicit-tail-call bail-out from #135291 now compiles all of FSharp.Core (exit 0); before it asserted on FSharpList<int>.ToString() (exit 133). I read the generated code for that method: the temp is set from SP and the tail call passes temp + 192. No wasm tests were run.

No regression test is included: reproducing needs IL with an explicit tail. call and only fails under a checked wasm JIT without the crossgen2 bail-out, so wasm R2R CI would not exercise it.

The branch history contains an earlier morph change and its revert; the net diff is only regallocwasm.cpp.

Resolves #135292

Note

This PR description was generated with GitHub Copilot.

…erence

When a call argument contains many stores (e.g. a long chain of calls
sharing temps), gtMayHaveStoreInterference gives up and conservatively
reports interference, which caused the wasm shadow stack pointer argument
to be evaluated into a temp. For a fast tail call the register allocator
expects that argument to be a GT_PHYSREG and asserted. The SP is never
stored to, so skip it when marking earlier args as needing temps.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 12580074-dce3-426d-bcc2-8c83aa5a4a7d
@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 Oct 6, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 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.

@jakobbotsch

Copy link
Copy Markdown
Member

For a fast tail call, WasmRegAlloc::CollectReferencesForCall expects the SP arg to be a GT_PHYSREG and asserted (regallocwasm.cpp:589).

Why does it do that?

There are many reasons operands of nodes can be evaluated into temporaries. We should not be making assumptions like this lightly.

@lewing

lewing commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

For a fast tail call, WasmRegAlloc::CollectReferencesForCall expects the SP arg to be a GT_PHYSREG and asserted (regallocwasm.cpp:589).

Why does it do that?

There are many reasons operands of nodes can be evaluated into temporaries. We should not be making assumptions like this lightly.

that is a question for @AndyAyersMS

lewing and others added 2 commits October 8, 2026 14:05
…e interference"

The regalloc no longer assumes the SP arg is a GT_PHYSREG, so avoiding the
spill is just an optimization and not needed for correctness.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 12580074-dce3-426d-bcc2-8c83aa5a4a7d
The regalloc no longer assumes the SP arg is a GT_PHYSREG, so avoiding the
spill is only an optimization and not needed for correctness.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 12580074-dce3-426d-bcc2-8c83aa5a4a7d
@lewing lewing changed the title [wasm][JIT] Don't spill the shadow stack pointer arg for store interference [wasm][JIT] Don't assume the SP arg of a fast tail call is a GT_PHYSREG Oct 8, 2026
@lewing

lewing commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Agreed. I've updated the PR to drop the assumption: CollectReferencesForCall now accepts a GT_LCL_VAR as well as a GT_PHYSREG for the SP arg, and only checks gtSrcReg for a GT_PHYSREG. The earlier morph change that avoided the spill is reverted.

The temp holds the same post-prolog SP value, so ADD(temp, FRAME_SIZE) is equally correct. I read the generated code for the repro method and the tail call passes temp + frameSize. I haven't run any wasm tests, and there is no regression test, for the reason in the description. @AndyAyersMS, can you confirm the SP arg can legitimately be a local here?

Note

This reply was generated with GitHub Copilot.

Comment on lines 588 to +591
assert(physReg != nullptr);
assert(physReg->OperIs(GT_PHYSREG));
assert(physReg->AsPhysReg()->gtSrcReg == m_perFuncletData[m_currentFunclet]->m_spReg);
assert(physReg->OperIs(GT_PHYSREG, GT_LCL_VAR));
assert(!physReg->OperIs(GT_PHYSREG) ||
(physReg->AsPhysReg()->gtSrcReg == m_perFuncletData[m_currentFunclet]->m_spReg));

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.

I think these asserts should just be deleted

This branch has not been deployed

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

Labels

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.

[wasm][JIT] regallocwasm asserts 'physReg->OperIs(GT_PHYSREG)' for fast tail call whose SP arg is spilled to a temp

2 participants