Skip to content

[browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT #130521

Open

Description

@pavelsavara

Enable PGO collection and consumption for Browser/CoreCLR ReadyToRun

Part of #130524.

This issue tracks two epics and two intended feature PRs:

Epic Scope Status
A: logical method-invocation counts Shared method-entry profiling for published R2R code and interpreter execution Implemented in draft #136860; not merged
B: detailed execution statistics Loop, basic-block and edge counts, including branch frequencies Deferred until after A; design and correctness work remain

Epic A does not establish desktop PGO parity or complete this issue's detailed-profile scope.

Goal

Collect profiles from representative Browser/CoreCLR application runs and consume them in a subsequent optimized ReadyToRun publish, without depending on tiered compilation or emitting an EventPipe event on every execution.

Published instrumentation is opt-in. The application and framework R2R images must be compiled with the requested profiling configuration; development builds cannot retrofit probes into prebuilt runtime-pack R2R images. Interpreter-only development collection can use the same runtime filter directly.

Epic A: logical method-invocation counts

Implementation: #136860.

Counting contract

  • Count each executed logical method invocation once, including actual inlinees, nested/conditional inlinees, and eligible intrinsic-expanded calls.
  • Aggregate standalone R2R bodies, inlined copies, and interpreter execution into the same PgoManager-owned counter for a logical method.
  • Shared reference-generic bodies may aggregate under their canonical identity; nonshared/value-type instantiations remain distinguishable. Do not infer concrete-instantiation counts from shared code.
  • Record entry in the interpreter's synthetic prologue, not an IL block that can be revisited by a backedge.
  • Use uint32 counters with intentional modulo-2^32 wraparound. Zero does not establish measured coldness.
  • Do not collect loop-head, block, edge, type, delegate, or value histograms in A.

Implementation and configuration

  • Emit direct load/add/store counter increments. Allocation and R2R import-cell resolution happen outside the per-invocation hot path; no PortableEntryPoint layout change is required.
  • Reuse allocPgoInstrumentationBySchema, PgoManager lifetime/enumeration, and EventPipe export.
  • Use WasmPgoInstrumentation=all or a MethodSet filter for published instrumentation, independently of CPU sampling.
  • Unify interpreter and R2R collection on DOTNET_WasmPgoInstrumentation, MethodEntryIntCount, and shared canonical counter storage.
  • Remove the separate recently introduced DOTNET_InterpPGO / DOTNET_InterpPGOMethods entry-plus-loop collection mode rather than maintaining two overlapping profiling systems.
  • Initially support single-threaded Browser/CoreCLR applications with diagnostics enabled and original IL retained.

Export and consumption

  • Reuse collectPgoTrace() and the existing EventPipe-to-MIBC pipeline. Counts are startup-cumulative and collection is one-shot per runtime instance.
  • Preserve explicit MethodEntryIntCount provenance. Entry-only data is not a complete BasicBlockIntCount profile and must not make unmeasured blocks appear cold.
  • Consume invocation counts in an optimized second publish by seeding conservative internal-flow synthesis. Synthesized branch probabilities are heuristic, not measured.
  • Keep invocation counts separate from CPU samples, ExclusiveWeight, and measured call-site frequencies.

Acceptance evidence

  • Exact real-browser MIBC counts for standalone/inlined calls, nested and folded inlinees, shared-reference versus value-type generics, intrinsic transformations, and interpreter fallback.
  • Correct uint32 high-bit export and wraparound.
  • Real trace -> numeric MIBC -> optimized R2R publish -> correct browser behavior, with matching producer/consumer managed identities.
  • PGO-only and PGO plus CPU-sampling configurations; filter selection and disabled/incremental transitions.
  • Document instrumentation overhead, emitted code/data size, and limitations without claiming negligible cost.

The draft PR contains implementation, tests, and measurements. Passing A's method-entry round trip must not be presented as validation of B's branch statistics.

Epic B: loop, block, and edge statistics

Extend A's allocation, addressing, configuration, export, and tooling foundation with detailed execution data.

Required design and correctness work

  • Define which blocks/edges need probes and how to reconstruct uninstrumented counts. Most or all blocks do not necessarily need an EventPipe event: hot-path probes update counters; export emits profile records later.
  • Establish correct handling of exception flow, locally caught throws, loops, shared generic code, interpreter/R2R mixed execution, and compiler transformations.
  • Preserve sufficient graph/schema identity between producer and consumer. Do not silently interpret incompatible block keys or an incomplete snapshot as a valid complete profile.
  • Choose counter width and overflow policy explicitly; do not assume the desktop 64-bit-count configuration applies unchanged to WASM32.
  • Distinguish measured branch/edge information from A's heuristic internal-flow synthesis.

Research findings motivating the separate epic

The initial sparse-edge experiments exposed correctness problems that method-entry counting does not solve:

  • A case with 100 invocations and 10 locally caught exceptions reconstructed 110 entries and zero catch-block weight.
  • A Debug-loop profile could be rejected by an optimized consumer of the same IL because block identities differed.
  • An incomplete activation/snapshot model could leave sparse counters at zero.

These are research results, not production-profile guarantees. B needs a proven scheme rather than simply enabling the existing sparse-edge path.

Acceptance criteria

  • Numeric loop, branch/switch, block and edge profiles match known executions, including exception-heavy cases.
  • Producer/consumer schema incompatibility and incomplete data are handled explicitly rather than producing false coldness.
  • A real Browser/CoreCLR trace converts to verified detailed MIBC data, is consumed by a second optimized publish, and preserves application behavior.
  • Measure additional instrumentation overhead and code/data size; retain A's independent entry-only option where appropriate.

Related work and sequencing

Note

This issue description was updated with GitHub Copilot assistance.

Activity

  1. added this to the Future milestone on Jul 10, 2026
  2. self-assigned this
    on Jul 10, 2026
  3. dotnet-policy-service commented on Jul 10, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
    See info in area-owners.md if you want to be subscribed.

  4. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Jul 10, 2026
  5. dotnet-policy-service commented on Jul 10, 2026

    @dotnet-policy-service
    Contributor

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

  6. changed the title [-][browser][coreCLR] R2R: X2 — Enable PGO instrumentation for the WASM RyuJIT[/-] [+][browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT[/+] on Jul 10, 2026
  7. modified the milestones: Future, 12.0.0 on Jul 13, 2026
  8. pavelsavara commented on Aug 24, 2026

    @pavelsavara
    MemberAuthor

    For the EventPipe-based PGO producer, the profile records should identify methods (and their blocks) purely by IL-level identity:

    • Module MVID — via LoaderModuleID on the MethodDetails event (→ module signature)
    • Metadata token — MethodToken
    • Generic instantiation — TypeID / BulkType for generic type/method arguments
    • IL offset — per block-count record (the BasicBlockIntCount schema entry's ILOffset)

    This is exactly what the existing MethodDetails (event 72) + BulkType (15) + MethodJitInstrumentationDataVerbose (298) events already carry, and it's what dotnet-pgo uses to resolve methods against the reference assemblies (matched by MVID + token). It is engine-independent, so the interpreter producer (#130517) and the RyuJIT producer tracked here emit the same schema and are resolved identically — the only requirement is that --reference points at the assemblies whose MVIDs match the trace (on browser/wasm that's the IL-trimmed linked/*.dll, since ILLink regenerates the MVID and webcil preserves it).

    This path does not need a PerfMap. PerfMap / .r2rmap is a native-address → method map used to symbolicate CPU samples; it's a different axis from instrumentation PGO. Because block counts are keyed by MVID + token + IL offset (never by native code addresses), no native symbolication is involved in producing or consuming the .mibc. PerfMap only becomes relevant for the sample-based path (dotnet-pgo --spgo) over R2R-compiled native code, where samples are native addresses that must be mapped back to those same method tokens — but that's out of scope for instrumentation PGO.

    Note

    This comment was generated with GitHub Copilot assistance.

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

Metadata

Metadata

Assignees

Labels

arch-wasmWebAssembly architecturearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIos-browserBrowser variant of arch-wasm

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions