Skip to content

[browser][coreCLR] EventPipe CPU sampling for RyuJIT/R2R on WASM - #132788

Draft
pavelsavara wants to merge 1 commit into
dotnet:mainfrom
pavelsavara:browser-coreclr-ep-cpu-sampling-r2r
Draft

[browser][coreCLR] EventPipe CPU sampling for RyuJIT/R2R on WASM#132788
pavelsavara wants to merge 1 commit into
dotnet:mainfrom
pavelsavara:browser-coreclr-ep-cpu-sampling-r2r

Conversation

@pavelsavara

Copy link
Copy Markdown
Member

Summary

Adds cooperative, self-triggered EventPipe CPU sampling for RyuJIT-generated R2R (native wasm) code on single-threaded browser/WASI, complementing the interpreter sampling delivered in #126324.

On single-threaded WASM there is no sampling thread that can suspend the target, so sampling has to be self-triggered by the instrumented code — the same approach the interpreter uses via INTOP_PROF_SAMPLEPOINT. This PR brings the equivalent to R2R code.

Status: draft / v1. Compiles and is validated at the codegen + SDK-plumbing level; the end-to-end runtime .nettrace capture is blocked by pre-existing experimental-path infra (see TODO below), not by this change.

How it works

  • New JIT helper CORINFO_HELP_WASM_PROF_SAMPLEPOINT and R2R helper READYTORUN_HELPER_WasmProfSamplepoint (0x119, R2R minor bumped to 27.1). The native shim JIT_WasmProfSamplepoint in vm/wasm/helpers.cpp (modeled on JIT_PollGC) publishes the caller's shadow SP, then JIT_WasmProfSamplepointImpl anchors a cooperative managed stack walk with an InlinedCallFrame / INLINED_PINVOKE_FROM_R2R derived from that SP and delegates to the shared SamplingProfiler_OnSamplepoint. Stays MODE_COOPERATIVE throughout — no GC transition — and only pushes the anchor frame / walks on an actual sample.
  • New node GT_WASM_PROF_SAMPLEPOINT + PHASE_WASM_PROF_INSTRUMENT (runs just before PHASE_WASM_VIRTUAL_IP so the per-block Virtual IP store lands ahead of each samplepoint). It emits a samplepoint at method entry and on every loop back-edge (a DFS back-edge walk; it deliberately does not skip BBF_GC_SAFE_POINT blocks, since with an uninstrumented framework a loop calling only BCL methods would otherwise never sample). Gated on the WasmPerformanceInstrumentation MethodSet filter — same config key and shared adaptive skip counter as the interpreter. Codegen emits it through genEmitHelperCall with the managed (sp, pep) ABI, exactly like the throw helpers (the proven WASM-R2R managed-helper path; the plain gtNewHelperCallNode path asserts IAT_VALUE and can't be used for R2R indirection cells).
  • crossgen2 maps the helper to its R2R cell; the WebAssembly SDK forwards the filter to crossgen (--codegenopt:WasmPerformanceInstrumentation=... via PublishReadyToRunCrossgen2ExtraArgs) and emits a <Warning> that framework/BCL frames are absent from samples (the runtime-pack R2R images are not instrumented).

What was validated

  • ✅ Browser CoreCLR clr+host build (VM + JIT clrjit_universal_wasm + crossgen2 + host) — clean.
  • ✅ crossgen2 with --codegenopt:WasmPerformanceInstrumentation=* over all of System.Private.CoreLib completes cleanly (robust across every method incl. loops, funclets, EH).
  • ✅ Samplepoints are actually emitted: the instrumented CoreLib R2R image is ~6% larger (+1.84 MB) than the uninstrumented baseline; the baseline (no filter) crossgens cleanly, so the phase correctly no-ops when unmatched.
  • ✅ libs build green; the browser-eventpipe sample builds for CoreCLR; the SDK warning fires on R2R publish.

TODO (for whoever continues after the blockers are resolved)

Runtime .nettrace capture is not yet done — blocked by pre-existing experimental CoreCLR-browser R2R sample-publish infrastructure, independent of this change:

  • Trimmed publish (PublishTrimmed=true, the intended path): intra-build self-lock — the publish rebuilds ILLink.Tasks.dll inside the same graph that has the trimmer task-host loaded (MSB3027/MSB3021). Not fixable via MSBUILDDISABLENODEREUSE=1 / DOTNET_CLI_USE_MSBUILD_SERVER=0 / /nodeReuse:false or pre-building ILLink.Tasks (all tried).
  • Untrimmed publish (PublishTrimmed=false): gets past that, then ManagedToNativeGenerator fails to resolve System.Private.CoreLib via MetadataLoadContext (src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs:100). This path got furthest and is likely the smaller lift.

Once a sample can be published: the browser-eventpipe sample only exposes collectCpuSamples interactively (DevTools console), so add a programmatic collectCpuSamples call to its main.js to capture a .nettrace headless, then assert the trace contains R2R frames with resolvable method names and that mixed R2R↔interpreter stacks unwind through WasmR2RToInterpreterThunkNode / WasmInterpreterToR2RThunkNode.

Deferred by design (follow-up PRs), per the agreed plan:

  • Inline-countdown fast path. v1 emits an unconditional helper call (skip counter lives inside the helper, like the shipped interpreter). The GCPOLL_INLINE-shaped inline countdown needs a new JIT-EE API (getAddrOfWasmSamplepointCounter, cloned from getAddrOfCaptureThreadGlobal) + an R2R IndirectWasmSamplepointCounter cell — deferred to keep v1 tractable.
  • Browser DevTools flame-chart profiler (PROF_ENTER/PROF_LEAVE). Needs an explicit BrowserProfiler_OnFrameUnwound hook in the R2R EH dispatcher (RyuJIT epilogs don't run on the throw path, so PROF_LEAVE would be missed and the shadow stack would drift). TARGET_BROWSER-only.

Notes for reviewers:

  • v1 registers the helper as a static native JITHELPER; getHelperFtnAddr auto-wraps it in a PortableEntryPoint on WASM (FEATURE_PORTABLE_ENTRYPOINTS).
  • Adds one CorInfoHelpFunc value → JIT-EE version GUID bumped.
  • WASM-only; NULL/no-op on other targets.

Note

This pull request (branch, commits, and description) was generated with the assistance of GitHub Copilot.

Adds cooperative, self-triggered EventPipe CPU sampling for RyuJIT-generated
R2R (native wasm) code on single-threaded browser/WASI, complementing the
interpreter sampling from dotnet#126324. On single-threaded WASM there is no sampling
thread, so sampling is driven by the instrumented code itself.

- New JIT helper CORINFO_HELP_WASM_PROF_SAMPLEPOINT and R2R helper
  READYTORUN_HELPER_WasmProfSamplepoint (0x119, R2R minor 27.1). Native shim
  JIT_WasmProfSamplepoint in vm/wasm/helpers.cpp (modeled on JIT_PollGC) anchors
  a cooperative managed stack walk via an InlinedCallFrame / INLINED_PINVOKE_FROM_R2R
  derived from the caller's shadow SP, then delegates to the shared
  SamplingProfiler_OnSamplepoint. No GC transition; only walks on an actual sample.
- New GT_WASM_PROF_SAMPLEPOINT node and PHASE_WASM_PROF_INSTRUMENT: emit a
  samplepoint at method entry and every loop back-edge for methods matching the
  WasmPerformanceInstrumentation MethodSet filter (same key and shared skip counter
  as the interpreter). Codegen emits it via genEmitHelperCall with the managed
  (sp, pep) ABI, like the throw helpers.
- crossgen2 maps the helper to its R2R cell; the WebAssembly SDK forwards the
  filter to crossgen (PublishReadyToRunCrossgen2ExtraArgs) and warns that framework
  and BCL frames are absent from samples (the runtime-pack R2R images are not
  instrumented).

v1 uses an unconditional helper call (skip counter inside the helper). The inline
countdown fast path and the browser DevTools flame-chart profiler (PROF_ENTER/LEAVE
plus an EH-dispatcher unwind hook) are deferred; see the PR description TODO.

Validated: browser CoreCLR clr+host build clean; crossgen2 with
WasmPerformanceInstrumentation=* over all of System.Private.CoreLib completes
cleanly and the instrumented R2R image is ~6% larger than baseline (samplepoints
emitted); the SDK warning fires on R2R publish. End-to-end runtime .nettrace
capture is blocked by pre-existing experimental CoreCLR-browser sample-publish
infrastructure issues (see TODO), not by this change.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant