dotnet / dotnet/runtime

[browser][coreCLR] R2R on WebAssembly (R1) — tracking

Open
#130,524 2 comments 0 reactions 1 assignee Claimed by @pavelsavara View on GitHub
arch-wasm area-CodeGen-Interpreter-coreclr os-browser
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

# R2R on CoreCLR-WebAssembly (R1) — tracking

**R1** adds precompiled native code to CoreCLR-on-WebAssembly without regressing the interpreter-based startup: full IL still downloads and starts on the interpreter, a profile-selected startup + JSON R2R composite runs the hot path native, and the rest streams in as R2R the interpreter finds hot. Prototype success = **correctness + a steady-state JSON speedup**; startup-time wins are deferred.

1. **Most of the hard runtime plumbing already exists.** WASM R2R compiles to a loadable module, method-subset compilation and profile ingestion exist, cross-module linking and entry-point patching work, and an upstream browser-host change adds per-assembly R2R build plus an async webcil-in-wasm loader with boot-config sizing. R1 is largely integration on top of these, not new runtime primitives.

2. **Profiling comes from the interpreter, in the standard PGO format (#130517, #130521, #130518).** The interpreter emits IL-offset-keyed **block counts** into the shared PGO data structure — block, not edge, counts because they are engine-independent and the precompiler derives branch weights from them (#130517). This needs the PGO feature enabled for the WASM JIT (#130521) and a dev-console JS trigger to flush and download the existing EventPipe trace mid-session (#130518); conversion to the precompiler's input reuses existing tooling.

3. **IL is sharded at build time by namespace (#130523).** A task splits trimmed, non-core assemblies into namespace-aligned shard assemblies behind a type-forwarding facade, merges cyclic namespaces so no shard-reference cycles form, packs toward an optimal size, and extends boot config with a new **R2R-only asset type** (IL shards stay ordinary assembly assets). It rewrites downstream assembly lists and is incremental.

4. **IL shards load on demand at runtime (#130515).** A consumer resolves and loads IL shards lazily via the facade forwarders plus boot-config metadata, at async-tolerant points. Synchronous JSPI fault-in is explicitly out of scope; behavior is identical to monolithic assemblies apart from load timing.

5. **The lazy R2R streaming loop is the defining R1 behavior — required even for the demo (#130516).** The same interpreter counters drive a live hot-threshold trigger that, with no diagnostics session open, ensures the IL shard is resident, asynchronously fetches the method's R2R shard, installs it via the browser host's async instantiate, and patches the entry point — the interpreter keeps running meanwhile, so no on-stack replacement. Without this there is no streaming path to demonstrate.

6. **R2R packaging is sequenced: per-assembly, then composite, then IL-reuse (#130519, #130522).** The first prototype uses per-assembly, non-stripped R2R (already the upstream path). Follow-ups cut size and improve code quality: a single startup composite with direct intra-composite calls (#130519), and R2R that drops its own metadata/IL to reuse the resident IL assembly — patching already-published entry points on load without touching on-stack frames, and working over sharded IL (#130522).

7. **Measurement is a harness plus a coverage signal (#130520).** It drives a JSON steady-state benchmark, asserts identical results across interpreter-only, startup-R2R, and post-tier-up runs, and measures R2R coverage by consuming the existing per-method entry-point EventPipe event. Coverage is the acceptance signal that the lazy loop actually activated shards; traced coverage runs stay separate from timing runs so overhead doesn't distort the speedup.

8. **Scope levers are set to the cheapest working option first.** Assembly-level shard granularity for now (true sub-assembly shards would need multi-image-per-module runtime work); non-stripped R2R first (#130522 is the size follow-up); per-assembly R2R first (#130519 is the composite follow-up); the core library is excluded from splitting. These keep the first milestone on already-working runtime primitives.

9. **Thin-slice-first sequencing.** **M0:** a hand/desktop-collected profile drives per-assembly R2R, loaded eagerly, proving JSON speedup + correctness (skips the two hardest blockers). **M1:** interpreter profiling (#130517/#130521/#130518) replaces the hand profile. **M2:** IL sharding and the live lazy loop (#130523/#130515/#130516) — the full R1 demo. **M3:** size/quality follow-ups (#130522/#130519) and measurement hardening (#130520).

10. **Top risks to watch.** PGO-without-tiered-compilation on WASM (#130521) is an unproven build shape; interpreter block boundaries must line up with the precompiler's at branch targets (validate in the harness); R2R-with-tiering-off must be confirmed across the whole path; and lazy R2R must stay off the critical path (async install, dedup, graceful stay-interpreted on load failure).

## Early Work items
- [ ] [Interpreter/EventPipe PGO instrumentation (block counts)](https://github.com/dotnet/runtime/issues/130517)
- [ ] [R2R that loads against IL already in memory](https://github.com/dotnet/runtime/issues/130522)
- [ ] [Interpreter live hotness trigger for on-demand R2R shard download](https://github.com/dotnet/runtime/issues/130516)

## Later Work items
- [ ] [IL splitter: shard trimmed assemblies by namespace](https://github.com/dotnet/runtime/issues/130523)
- [ ] [Runtime shard consumer: lazy IL shard load](https://github.com/dotnet/runtime/issues/130515)
- [ ] [Enable PGO instrumentation for the WASM RyuJIT](https://github.com/dotnet/runtime/issues/130521)
- [ ] [R2R composite for the startup set](https://github.com/dotnet/runtime/issues/130519)
- [ ] [Measurement harness: JSON steady-state, correctness, and R2R coverage](https://github.com/dotnet/runtime/issues/130520)
- [ ] [JS trigger to capture/download the PGO trace](https://github.com/dotnet/runtime/issues/130518)

## Foundation

Builds on #129634 (browserhost to load R2R): per-assembly R2R build + async webcil-in-wasm browser loader + boot-config sizing.

## Decisions

Assembly-level shards now · non-stripped R2R first (#130522 follow-up) · per-assembly R2R first (#130519 follow-up) · profile via interpreter EventPipe + existing conversion · coverage via the existing entry-point event · JSPI out of scope.

> [!NOTE]
> This tracking issue was drafted with GitHub Copilot assistance.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.