Skip to content

[wasm] Delete hand-written R2R-to-interpreter thunks; use crossgen2's - #132965

Closed
pavelsavara wants to merge 6 commits into
dotnet:mainfrom
pavelsavara:wasm_fix_r2r_interp_thunks
Closed

[wasm] Delete hand-written R2R-to-interpreter thunks; use crossgen2's#132965
pavelsavara wants to merge 6 commits into
dotnet:mainfrom
pavelsavara:wasm_fix_r2r_interp_thunks

Conversation

@pavelsavara

Copy link
Copy Markdown
Member

Addresses @jkotas's feedback on #132926: rather than grow another static table of hand-written R2R-to-interpreter thunks, delete them and rely on the ones crossgen2 already emits.

What

The R2R-to-interpreter transition thunks were a hand-written table in vm/wasm/helpers.cpp (g_wasmPortableEntryPointThunks + the CallInterpreter_* functions). crossgen2 already emits these thunks into each R2R image (WasmR2RToInterpreterThunkNode, keyed "I"+signature, from #127483), and the runtime discovers them by string via LookupPregeneratedThunkByString. The static table was therefore a buggy, incomplete duplicate that shadowed the correct crossgen2 thunks — it was checked first, yet had no float/double-return shape, among others.

This removes the table and routes LookupPortableEntryPointThunk solely through the R2R string hash. Pure interpreter keeps working via the existing deferral path (EnsurePortableEntryPointIsCallableFromR2RAddPendingPortableEntryPointThunk, resolved when an R2R module loads). helpers.cpp: −368 / +5.

Test

Adds src/tests/readytorun/wasm/WasmInterpreterTransitions, exercising both transition directions across struct (S8/S12/S16), scalar, void, and float/double returns. [BypassReadyToRun] + composite R2R ensures every cross-call actually hits a thunk (a wrong parameter order still type-checks under call_indirect, so the cases assert pinned values).

Validation (local, Windows host)

  • Browser CoreCLR: WasmInterpreterTransitions passes (Expected 100 / Actual 100).
  • Sibling browser R2R tests pass: WasmR2RStructAlignment, Runtime_131640.
  • WasmArgumentLayoutTests (crossgen2 lowering, unchanged here): 52 / 52.
  • console-node CoreCLR sample runs clean.
  • helpers.cpp compiles for both browser and wasi (the wasi target runtime builds).

Commits

  1. [wasm] Treat wasi as a cross-target in the Windows build scripts — a separable wasi-on-Windows build fix (also carried in [wasm] Generate the R2R-to-interpreter thunk table #132926) so the wasi CoreCLR build configures on a Windows host. This is only one of the two separable wasi build fixes; the configureplatform.cmake cross-components fix is not included, so a full wasi build is not green from this PR alone. Can be split into its own PR if preferred.
  2. [wasm] Delete hand-written R2R-to-interpreter thunks; use crossgen2's.

Draft while gathering CI signal.

Note

This PR description was generated with AI assistance.

build-runtime.cmd and build-native.cmd only treated android and browser as cross-targets, so a wasi build on a Windows host ran copy_version_files.cmd (which copies only *.h/*.rc) instead of copy_version_files.ps1, which also generates _version.c, and CMake configure then failed with missing source files.
The R2R-to-interpreter transition thunks were a hand-written table in vm/wasm/helpers.cpp (g_wasmPortableEntryPointThunks plus the CallInterpreter_* functions). crossgen2 already emits these thunks into each R2R image (WasmR2RToInterpreterThunkNode, keyed 'I'+signature) and the runtime discovers them by string via LookupPregeneratedThunkByString, so the static table is a buggy, incomplete duplicate that shadows the correct crossgen2 thunks - it had no float/double-return shape, among others.

Remove the table and route LookupPortableEntryPointThunk solely through the R2R string hash. Pure interpreter keeps working via the existing deferral path (EnsurePortableEntryPointIsCallableFromR2R -> AddPendingPortableEntryPointThunk).

Add src/tests/readytorun/wasm/WasmInterpreterTransitions, which exercises both transition directions across struct, scalar, void, and float/double returns.
@azure-pipelines

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

@pavelsavara pavelsavara added the arch-wasm WebAssembly architecture label Aug 31, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Comment thread src/coreclr/vm/wasm/helpers.cpp Outdated
Comment thread src/coreclr/vm/wasm/helpers.cpp Outdated
@pavelsavara

pavelsavara commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

Why the pregenerated portable-entry-point thunk table cannot be deleted

The R2R→interpreter thunk is needed whenever a caller reaches an interpreted method through a materialized native entry point — i.e. any call_indirect: ldftn/delegate/virtual dispatch, plus every R2R→interpreter call. On wasm that entry point is an index into __indirect_function_table, and the thunk is the function that builds the interpreter frame and enters it with the right signature.

crossgen2 emits these thunks (WasmR2RToInterpreterThunkNode, keyed 'I'+sig) into each R2R image, discovered at runtime via LookupPregeneratedThunkByString. That source exists only for shapes a loaded, crossgen'd module rooted.

Pure interpreter loads zero R2R images. With DOTNET_ReadyToRun=0, or simply when nothing was crossgen'd (dev inner-loop), no R2R module is present.

But the interpreter still needs a portable entry point the instant managed code takes a delegate/function-pointer to an interpreted method.

This leaves us with

  • pre-generated C thunks for known signatures.
  • crossgen'd thunks
    • R2R webcil which even pure interp would load module to be able to install those thunks
    • LLVM linkable .o file that dotnet.native.wasm would link

Both options are incomplete because we don't pre-create thunks for signatures in the app assemblies.
Doing so means running crossgen in the dev-loop too.

None of this is on critical path for me, to unblock Blazor on R2R. I'm closing this and we will re-consider later.

I'm back to #132926

cc @jkotas @davidwrighton

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants