Visitar URL original
[browser][CoreCLR] Collect ReadyToRun PGO with indexed schemas by pavelsavara · Pull Request #137491 · dotnet/runtime · GitHub
Skip to content

[browser][CoreCLR] Collect ReadyToRun PGO with indexed schemas - #137491

Closed
pavelsavara wants to merge 2 commits into
dotnet:mainfrom
pavelsavara:browser-coreclr-r2r-pgo-schema-table
Closed

pavelsavara wants to merge 2 commits into
dotnet:mainfrom
pavelsavara:browser-coreclr-r2r-pgo-schema-table

Conversation

@pavelsavara

Copy link
Copy Markdown
Member

Summary

Enable opt-in PGO collection from published Browser/CoreCLR ReadyToRun applications and consume the resulting MIBC in a subsequent optimized, partial ReadyToRun publish.

Design

  • Emit 64-bit IL-backed basic-block counters through the normal JIT instrumentation phases, including inlinees. Preserve original pre-import IL block identities rather than instrumenting synthetic compiler blocks.
  • Preserve explicit invocation-only records for expanded intrinsics and coarse interpreter fallback. Intrinsic expansion leaves an IR marker that is materialized during normal instrumentation; invocation contributions remain distinct from measured body-block data.
  • Add a dedicated PgoInstrumentationSchemas ReadyToRun section and an indexed PgoInstrumentationData fixup. Deterministic, deduplicated schemas describe instrumentation kinds, IL offsets, counts, and aligned byte offsets into counter storage. The ReadyToRun format becomes 34.1.
  • Resolve each import cell to a counter allocation base and emit direct, unsynchronized 64-bit load/add/store probes at schema-defined offsets. Exact layouts share allocations; incompatible live layouts retain separate storage.
  • Aggregate compatible layouts per logical method when exporting through the existing EventPipe PGO pipeline. Validate schema and IL identity, retain distinct invocation contributions, and avoid replacing storage referenced by generated code.
  • Extend profile tooling and consumption to preserve these records, resolve canonical generic profiles, reconstruct uniquely determined block flows before heuristic repair, and combine omitted-body invocation contributions without treating them as measured branch counts.

Integration and scope

  • Forward WasmPgoInstrumentation=all or a MethodSet filter through application and framework ReadyToRun publishing, independently of CPU sampling. Recompile framework images when instrumentation changes and invalidate the corresponding incremental stamps.
  • Use DOTNET_WasmPgoInstrumentation for ordinary interpreter collection through the same profiling infrastructure.
  • Support single-threaded Browser/CoreCLR publishing with diagnostics enabled and original IL retained. Reject unsupported configurations explicitly.
  • Keep allocation and EventPipe export outside the per-block hot path. This scope does not add edge probes, type/value histograms, or precise exception-flow profiling.
  • Canonicalize body-only readiness imports so eager activation does not retain an exact constrained owner and bind dependent assemblies prematurely.
  • Add schema/layout, compiler, profile-tooling, and browser round-trip regressions, and document configuration and limitations.

Tracking: #130521.

Note

This PR description was generated with GitHub Copilot assistance.

Collect 64-bit IL block profiles through normal JIT instrumentation and
preserve distinct intrinsic and interpreter invocation contributions.
Resolve indexed R2R schemas to stable counter storage and carry the
profiles through EventPipe, MIBC, and partial ReadyToRun consumption.

Add publish configuration, schema and compiler regressions, browser
round trips, and usage documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pavelsavara

Copy link
Copy Markdown
Member Author

Design differences from #136860

This is a separate implementation on a fresh main baseline. The earlier PR remains unchanged.

Area Previous design Current design
Ordinary R2R bodies Logical invocation-only profiling 64-bit IL-backed basic-block profiling through the normal JIT instrumentation phases
R2R schema transport Schema payload carried directly in the storage fixup Dedicated, deterministic, deduplicated schema table; method fixups carry its index
Inlinees and expanded intrinsics Entry-oriented instrumentation, with importer-time work for expanded intrinsic invocations Standard inlinee block instrumentation; intrinsic import leaves an IR marker and counter materialization happens during normal instrumentation
Counter provenance Predominantly MethodEntryLongCount BasicBlockLongCount for executed bodies, plus distinct MethodEntryLongCount contributions for omitted bodies and coarse fallback
Consumption Invocation counts seed heuristic internal-flow synthesis Measured block counts support loop/branch-flow reconstruction; heuristics remain for underdetermined or transformed flow
Logical method storage Entry-oriented schema-based storage Exact multi-counter layouts share allocations; incompatible live layouts coexist and compatible data is aggregated for export

The fundamental change is richer execution information, not more precise counting of every logical invocation around exceptions. Reusing JIT instrumentation avoids a separate importer-driven block profiler and accommodates multiple counters per method.

IL0 is not inherently an entry counter: it can be a loop backedge target. The consumer therefore reconstructs method-entry flow from the measured graph where possible rather than equating IL0 hotness with invocation count. Synthetic/split compiler blocks do not acquire duplicate IL-block probes.

Invocation-only records are still necessary for intrinsic expansions and coarse interpreter execution. They describe omitted-body contributions, not a fabricated complete block profile. A method may legitimately have both kinds of data; the consumer preserves measured branch information while adding invocation contributions.

Several foundations are deliberately retained: 64-bit counters, allocation-base-plus-offset addressing, the shared WasmPgoInstrumentation configuration, the existing EventPipe-to-MIBC pipeline, canonical method attribution, and independent CPU sampling. They should not be mistaken for new differences.

The initial scope remains single-threaded Browser/CoreCLR with diagnostics and original IL. It does not introduce edge probes, histogram collection, or precise exception-flow parity. Training incurs counter-update overhead; these changes do not establish an application-level consumer speedup.

Note

This comparison was generated with GitHub Copilot assistance.

@pavelsavara

Copy link
Copy Markdown
Member Author

Retained MIBC comparison and interpretation

Compared the latest successful Release, unfiltered PGO browser runs from the two implementations, decoding both MIBCs with the same current dotnet-pgo reader:

  • Previous: November 11 run blazor_method_entry_pgo_Release_False_xkxx5sas_hr5, method-entry.mibc.
  • Current: November 13 run blazor_basic_block_pgo_Release_False_bhaf03dq_apt, basic-block.mibc.

Coverage and profile size

Metric Previous Current
Method records in the MIBC dump 8,210 8,199
Method records with counters 8,126 8,118
Basic-block counter records 0 41,769
Invocation counter records 8,126 647
Compressed MIBC bytes 130,480 224,165

The compressed profile grows 71.8%, principally because ordinary methods now carry multiple block records instead of one invocation record. Both files remain MIBC format 1.1: the indexed schema table changes the R2R producer's wire format, not the MIBC container format.

The current profile contains 7,471 block-only, 598 invocation-only, and 49 mixed method records, plus 81 without counters.

Method coverage is nearly unchanged. The display-name sets share 8,187 names, with 11 current-only and 22 previous-only. Each dump also has one repeated display name for a UInt128.op_Explicit overload, so display-name set counts are not the raw method-record counts.

Both have 225 Vector128/PackedSimd-related method records. This comparison does not show a new large set of SIMD roots: the improvement is richer information about already-covered algorithms. For example, Ascii.WidenAsciiToUtf1_Vector<Vector128<byte>, Vector128<ushort>> retains 723 entry-block executions, but now also exposes its internal block/loop counts and unexecuted paths.

These are representative retained runs, not a controlled same-build experiment. Main revisions, test sources, and runtime execution differ. Some set differences are explicitly new/removed scenarios; the remainder cannot all be attributed to instrumentation design. Profile candidates also do not guarantee additional standalone R2R bodies: canonical sharing, eligibility, dependencies, and inlining still determine the output.

Concrete counter differences

Scenario Previous Current
DirectCalls 1 invocation Entry 1, loop body 10, loop test 11, exit 1
EntryAndBackedge 2 invocations Entry 2, loop body 7, loop test 9, exit 2
CpuSamplingTarget 5 invocations Entry 5, loop body 500,000, loop test 500,005, exit 5
Helper / NestedInlinee 15 invocations each 15 executions of each single-block body
Shared-reference / value-type GenericEntries 7 / 6 invocations 7 / 6 body-block executions

The new IlZeroBackedge scenario records 7 at IL0 and 1 at exit, although there was only one invocation. This is why IL0 cannot automatically be consumed as an entry count: graph flow must distinguish backedge executions from external entries.

Zero-valued body blocks now express unexecuted paths in the measured body schema, not a guess derived from entry hotness. Invocation-only contributions remain separate:

  • A WebAssembly InlineArrayElementRef previously recorded 9 invocations; now it records 3 body executions + 6 omitted-body invocations.
  • string.EndsWith(string, StringComparison) retains 284 invocation contributions even though its body-block allocation is zero-valued. Ignoring the invocation record would incorrectly erase that hotness.

What the consumer demonstrably changes

The retained consumer listing for EntryAndBackedge changes from Synthesized PGO to Static PGO, preserving entry count 2. The new IL0-backedge case reports Static PGO, entry count 1, rather than confusing its seven IL0 executions with seven invocations.

However, DirectCalls and GenericCalls still report Synthesized PGO in their final listings. Both retain the expected inlinees with PGO data. Richer MIBC data does not establish that every transformed method ends with a wholly measured static profile.

The practical benefit is measured loop hotness and more constrained branch-flow reconstruction instead of relying only on heuristic internal flow seeded from invocation counts. This is evidence of better profile information and consumption, not a measured application speedup or proof of complete exception/edge-profile parity.

Note

These comparison notes were generated with GitHub Copilot assistance.

@azure-pipelines

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

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging @dotnet/jit-contrib for JIT-EE GUID update

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/crossgen-contrib
See info in area-owners.md if you want to be subscribed.

@pavelsavara pavelsavara added the arch-wasm WebAssembly architecture label Nov 13, 2026
@pavelsavara pavelsavara added this to the 12.0.0 milestone Nov 13, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Preserve legacy interpreter profiling outside single-threaded Browser,
use per-method schema allocation chains, and handle sparse flow inference
conservatively when exception exits are not measurable.

Include the pending main merge and all local PGO fixes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pavelsavara

Copy link
Copy Markdown
Member Author

Superseded by dotnet/runtime#137502.

The branch was copied into dotnet/runtime as pavelsavara/browser-coreclr-r2r-pgo-schema-table at commit 06fa85f1de97d2fb9d49281677436b6387aba9e0. Please continue review and further changes on the replacement PR; existing discussion remains here for reference.

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

Labels

arch-wasm WebAssembly architecture area-ReadyToRun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant