Skip to content

[Wasm RyuJit] Call Codegen Fixes for R2R#126778

Open
adamperlin wants to merge 3 commits intodotnet:mainfrom
adamperlin:adamperlin/wasm-fix-call-codegen
Open

[Wasm RyuJit] Call Codegen Fixes for R2R#126778
adamperlin wants to merge 3 commits intodotnet:mainfrom
adamperlin:adamperlin/wasm-fix-call-codegen

Conversation

@adamperlin
Copy link
Copy Markdown
Contributor

This is another cherry-picked commit from #126662; There are a few cases in Wasm codegen where we needed an extra layer of indirection to load a PEP or call target, since the host provides symbols which resolve to indirection cells for calls, and we have indirection cell -> PEP -> target method.

davidwrighton and others added 3 commits April 9, 2026 13:44
Copilot AI review requested due to automatic review settings April 10, 2026 23: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 Apr 10, 2026
@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.

@adamperlin adamperlin changed the title [Wasm RyuJit] Call Codegen Fixes [Wasm RyuJit] Call Codegen Fixes for R2R Apr 10, 2026
@adamperlin adamperlin requested review from AndyAyersMS and kg April 10, 2026 23:34
Copy link
Copy Markdown
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 adjusts WebAssembly RyuJIT call lowering/emission for ReadyToRun (R2R) so call targets and PortableEntryPoints (PEPs) are loaded through the correct number of indirections when the host exposes symbols as indirection cells (indirection cell -> PEP -> target).

Changes:

  • Add an extra indirection when materializing the R2R indirection-cell argument on WASM so the value passed reflects the PEP rather than the cell itself.
  • Update WASM direct-call lowering for IAT_PVALUE to dereference through indirection cell -> PEP -> target.
  • Update WASM helper-call emission to load the PEP from the indirection cell (for managed helpers) and to load the final call target via an additional dereference.

Reviewed changes

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

File Description
src/coreclr/jit/morph.cpp Adds a WASM-specific extra indirection when preparing the R2R indirection-cell argument so the runtime sees the expected PEP pointer.
src/coreclr/jit/lower.cpp Ensures WASM IAT_PVALUE direct call targets are lowered with an additional dereference to reach the actual dispatch target.
src/coreclr/jit/codegenwasm.cpp Fixes helper-call emission to load the PEP from the indirection cell and to load the indirect call target by dereferencing the PEP.

// Push the call target onto the wasm evaluation stack by dereferencing the PEP.
// Push the call target onto the wasm evaluation stack by dereferencing the indirection cell
// and then the PEP pointed to by the indirection cell.
assert(helperFunction.accessType == IAT_PVALUE);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See if it's possible to use the new ADDR_REL reloc type for this to avoid the i32.const <address>; i32.load pattern.

@AndyAyersMS
Copy link
Copy Markdown
Member

Maybe a picture would be useful here?

I imagine it something like this: We have an indirection cell that refers to a PEP which refers to a function index, which we then call indirectly.

cellAddr->AsIntCon()->gtTargetHandle = (size_t)call->gtCallMethHnd;
#endif
GenTree* indir = Ind(cellAddr);
#ifdef TARGET_WASM
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.

The extra indirections related to portable entrypoints may want to be under if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS))

#endif
GenTree* indir = Ind(cellAddr);
#ifdef TARGET_WASM
indir = Ind(indir); // WebAssembly "function pointers" are actually PortableEntryPoint pointers, and
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.

The portable entrypoint needs to be also passed as the last argument. Where does that happen when we take this path?

#endif
GenTree* indir = Ind(cellAddr);
#ifdef TARGET_WASM
indir = Ind(indir); // WebAssembly "function pointers" are actually PortableEntryPoint pointers, and
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.

IAT_PPVALUE exists. Is there a reason it is not used by crossgen when it seems to be exactly what it wants here? It feels odd to return IAT_PVALUE and then have the JIT dereference it twice.

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.

By the way, I would expect the optimization that is disabled for wasm above to result in large size savings if it was enabled.

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.

It feels odd to return IAT_PVALUE and then have the JIT dereference it twice.

I think IAT_PVALUE is correct here, and it is going to make more sense once it follows the portable entry point ABI (see my other 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.

The portable entry point argument is being added in morph, near the morph change in this PR. I am not totally sure what it changes related to the number of indirections needed from the value returned by getFunctionEntryPoint though.
Do you mean that only one indirection is needed on top of the portable entry point argument? It is the optimization that is disabled above -- avoiding the duplication of the indirection cell.

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.

IAT_PVALUE?

It is notable that with the current Jit-EE contract for portable entrypoints (where they're considered "direct" function pointers), the IAT_* values are now context-dependent. E. g. the function for SGCT PIs also returns "direct" IAT_VALUE values, but they won't need to be indirected.

FWIW, I think that in the ideal world, we would only have one place where the portable entrypoint flag would matter - with indirect managed calli, and the rest of the Jit would continue to operate on "native function pointer" semantics. But it's not where where we are going here it seems.


#if defined(TARGET_WASM)
// On WASM, the address in the R2R table is actually the address of something that should
// be treated as a PortableEntryPoint. To actually dispatch, we need to indirect once more.
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.

What is the reason we can't avoid this indirection? Requirement to allocate "runtime-shaped" PEPs dynamically (can the R2R cell be a superclass of a PEP layout-wise, or vice-versa)?

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.

7 participants