Repository navigation
Description
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=allor 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_InterpPGOMethodsentry-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
MethodEntryIntCountprovenance. Entry-only data is not a completeBasicBlockIntCountprofile 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
- Interpreter producer: [browser][coreCLR] R2R: Interpreter PGO instrumentation (block counts) #130517, implemented by merged Interpreter block-count PGO for WebAssembly CoreCLR #132721. Epic A now unifies its collection path with R2R method-entry profiling.
- R2R cooperative CPU sampling: merged [browser][CoreCLR] Enable EventPipe CPU sampling for RyuJIT/R2R on WebAssembly #132788. Its addressing/metadata infrastructure is useful prior art, but CPU samples are not invocation or branch counts.
- Epic A implementation: draft [browser][CoreCLR] Collect method-entry PGO profiles from R2R #136860.
- Land A's supported method-entry foundation first, then implement B in a separate PR. Keep this tracking issue open until its detailed-profile scope is completed or explicitly moved to a separate tracking issue.
Note
This issue description was updated with GitHub Copilot assistance.
Activity
dotnet-policy-service commentedon Jul 10, 2026
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Jul 10, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
[-][browser][coreCLR] R2R: X2 — Enable PGO instrumentation for the WASM RyuJIT[/-][+][browser][coreCLR] R2R: Enable PGO instrumentation for the WASM RyuJIT[/+]pavelsavara commentedon Aug 24, 2026
For the EventPipe-based PGO producer, the profile records should identify methods (and their blocks) purely by IL-level identity:
LoaderModuleIDon theMethodDetailsevent (→ module signature)MethodTokenTypeID/BulkTypefor generic type/method argumentsBasicBlockIntCountschema entry'sILOffset)This is exactly what the existing
MethodDetails(event 72) +BulkType(15) +MethodJitInstrumentationDataVerbose(298) events already carry, and it's whatdotnet-pgouses 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--referencepoints at the assemblies whose MVIDs match the trace (on browser/wasm that's the IL-trimmedlinked/*.dll, since ILLink regenerates the MVID and webcil preserves it).This path does not need a PerfMap. PerfMap /
.r2rmapis 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.