You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
IL is sharded at build time by namespace ([browser][coreCLR] R2R: IL splitter — shard trimmed assemblies 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.
IL shards load on demand at runtime ([browser][coreCLR] R2R: Runtime shard consumer — lazy IL shard load #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.
The lazy R2R streaming loop is the defining R1 behavior — required even for the demo ([browser][coreCLR] R2R: Interpreter live hotness trigger for on-demand R2R shard download #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.
Measurement is a harness plus a coverage signal ([browser][coreCLR] R2R: Measurement harness — JSON steady-state, correctness, and R2R coverage #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.
Top risks to watch. PGO-without-tiered-compilation on WASM ([browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT #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).
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.
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.
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.
Profiling comes from the interpreter, in the standard PGO format ([browser][coreCLR] R2R: Interpreter PGO instrumentation (block counts) #130517, [browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT #130521, [browser][coreCLR] R2R: Dedicated JS trigger to capture and download the PGO trace #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 ([browser][coreCLR] R2R: Interpreter PGO instrumentation (block counts) #130517). This needs the PGO feature enabled for the WASM JIT ([browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT #130521) and a dev-console JS trigger to flush and download the existing EventPipe trace mid-session ([browser][coreCLR] R2R: Dedicated JS trigger to capture and download the PGO trace #130518); conversion to the precompiler's input reuses existing tooling.
IL is sharded at build time by namespace ([browser][coreCLR] R2R: IL splitter — shard trimmed assemblies 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.
IL shards load on demand at runtime ([browser][coreCLR] R2R: Runtime shard consumer — lazy IL shard load #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.
The lazy R2R streaming loop is the defining R1 behavior — required even for the demo ([browser][coreCLR] R2R: Interpreter live hotness trigger for on-demand R2R shard download #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.
R2R packaging is sequenced: per-assembly, then composite, then IL-reuse ([browser][coreCLR] R2R: R2R composite for the startup set #130519, [browser][coreCLR] R2R: R2R that loads against IL already in memory #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 ([browser][coreCLR] R2R: R2R composite for the startup set #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 ([browser][coreCLR] R2R: R2R that loads against IL already in memory #130522).
Measurement is a harness plus a coverage signal ([browser][coreCLR] R2R: Measurement harness — JSON steady-state, correctness, and R2R coverage #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.
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 ([browser][coreCLR] R2R: R2R that loads against IL already in memory #130522 is the size follow-up); per-assembly R2R first ([browser][coreCLR] R2R: R2R composite for the startup set #130519 is the composite follow-up); the core library is excluded from splitting. These keep the first milestone on already-working runtime primitives.
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 ([browser][coreCLR] R2R: Interpreter PGO instrumentation (block counts) #130517/[browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT #130521/[browser][coreCLR] R2R: Dedicated JS trigger to capture and download the PGO trace #130518) replaces the hand profile. M2: IL sharding and the live lazy loop ([browser][coreCLR] R2R: IL splitter — shard trimmed assemblies by namespace #130523/[browser][coreCLR] R2R: Runtime shard consumer — lazy IL shard load #130515/[browser][coreCLR] R2R: Interpreter live hotness trigger for on-demand R2R shard download #130516) — the full R1 demo. M3: size/quality follow-ups ([browser][coreCLR] R2R: R2R that loads against IL already in memory #130522/[browser][coreCLR] R2R: R2R composite for the startup set #130519) and measurement hardening ([browser][coreCLR] R2R: Measurement harness — JSON steady-state, correctness, and R2R coverage #130520).
Top risks to watch. PGO-without-tiered-compilation on WASM ([browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT #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
Later Work items
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.