Skip to content

[Wasm Ryujit] Wasm SP is implicitly referenced#124115

Open
AndyAyersMS wants to merge 1 commit intodotnet:mainfrom
AndyAyersMS:WasmSPImplicitReference
Open

[Wasm Ryujit] Wasm SP is implicitly referenced#124115
AndyAyersMS wants to merge 1 commit intodotnet:mainfrom
AndyAyersMS:WasmSPImplicitReference

Conversation

@AndyAyersMS
Copy link
Member

Fixes an issue where SP mentions are introduced late (say from a throw helper).

Fixes an issue where SP mentions are introduced late (say from a throw helper).
Copilot AI review requested due to automatic review settings February 7, 2026 00:26
@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 Feb 7, 2026
@dotnet-policy-service
Copy link
Contributor

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

@AndyAyersMS
Copy link
Member Author

@SingleAccretion not sure if this is what we agreed on or you have other ideas.

Fixes things like

Assertion failed '(WasmValueType::Invalid < wasmType) && (wasmType < WasmValueType::Count)' 
   in 'System.Reflection.BlobUtilities:WriteByte(byte[],int,byte)' during 'Generate code'

where the only SP mention is in the bounds check helper.

@AndyAyersMS
Copy link
Member Author

fyi @dotnet/jit-contrib

Copy link
Contributor

Copilot AI left a comment

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 ensures the Wasm shadow stack pointer local (lvaWasmSpArg) is treated as “implicitly referenced” so it won’t be considered unused when references to it are introduced later in compilation (e.g., via late-added helper calls).

Changes:

  • Mark the Wasm stack pointer argument local as lvImplicitlyReferenced.
  • Mark the reverse-P/Invoke Wasm stack pointer temp local as lvImplicitlyReferenced when it is allocated.

Comment on lines +529 to +532
lvaWasmSpArg = lvaGrabTemp(false DEBUGARG("SP"));
LclVarDsc* varDsc = lvaGetDesc(lvaWasmSpArg);
varDsc->lvType = TYP_I_IMPL;
varDsc->lvImplicitlyReferenced = 1;
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

lvaAllocWasmStackPtr now does lvaGrabTemp(...) followed by manually setting lvImplicitlyReferenced. Since there is an existing helper lvaGrabTempWithImplicitUse(...) (which encapsulates this pattern and is used elsewhere), consider using it here to avoid duplicating the implicit-use bookkeeping and reduce the chance of future drift.

Suggested change
lvaWasmSpArg = lvaGrabTemp(false DEBUGARG("SP"));
LclVarDsc* varDsc = lvaGetDesc(lvaWasmSpArg);
varDsc->lvType = TYP_I_IMPL;
varDsc->lvImplicitlyReferenced = 1;
lvaWasmSpArg = lvaGrabTempWithImplicitUse(false DEBUGARG("SP"));
LclVarDsc* varDsc = lvaGetDesc(lvaWasmSpArg);
varDsc->lvType = TYP_I_IMPL;

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like the suggestion here would work, and also it looks like lvaGrabTempWithImplicitUse accounts for inlining compilations?

Copy link
Member Author

Choose a reason for hiding this comment

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

It turns out we will never call either lvaAllocWasmStackPtr or lvaInitWasmStackPtr in an inlinee context. Perhaps we should add an assert.

lvaAllocWasmStackPtr will be used to establish a stack pointer local in managed methods that are invoked with a native calling convention (and so have no stack pointer arg). This can only happen in a root method (reverse pinvoke/unmanaged callers only) as inlinees always have a managed calling convention. The prolog of these methods will get the stack pointer global and use that to set up the stack pointer local.

For lvaInitWasmStackPtr the story is a bit more complicated.

In the root method we eagerly create lclvars for all the args and locals. But not so for inlinees. The method that sets up the argument and local lclvars is lvaInitTypeRef. After it does some initial accounting it bails out for inlinees.

Instead, inlinee args and locals generally are handled in one of 3 ways:
(1) if an arg/local is never referred to, it is simply ignored
(2) if an arg is not modifiable in the inlinee, and the caller parameter tree is suitably small and invariant, we try and directly substitute the caller's parameter tree for uses of the arg (see impInlineFetchArg). We call this the "direct sub" optimization.
(3) or, we allocate a new temporary lclvar for use in the inline body (via impInlineFetchLocal or the remaining parts of impInlineFetchArg).

Copy link
Contributor

@SingleAccretion SingleAccretion left a comment

Choose a reason for hiding this comment

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

I think the ordering issue between the stack level setter and liveness / ref counting still needs to be fixed, but this is also a correct change (since there are implicit references at nodes like LCLHEAP).

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.

3 participants