Skip to content

[wasm][coreclr] Default browser ReadyToRun publishing to composite - #136486

Merged
radekdoulik merged 3 commits into
dotnet:mainfrom
radekdoulik:radekdoulik-wasm-composite-r2r
Nov 3, 2026
Merged

radekdoulik merged 3 commits into
dotnet:mainfrom
radekdoulik:radekdoulik-wasm-composite-r2r

Conversation

@radekdoulik

Copy link
Copy Markdown
Member

Summary

Default CoreCLR browser-wasm ReadyToRun publishing to composite mode. ReadyToRun itself remains opt-in through PublishReadyToRun=true; PublishReadyToRunComposite=false retains per-assembly publishing.

  • Apply the default after project properties have been read but before the SDK sets its per-assembly default. Changing only the late CoreCLR targets would not override the SDK's already-populated false value.
  • Set the same default in the in-tree browser props: library tests import these after the SDK's early-defaults hook has already run.
  • Forward the composite property through the existing nested sample-publish mechanism so an explicit per-assembly opt-out is preserved.
  • Make the existing composite publish tests exercise the implicit default, retaining explicit per-assembly coverage.
  • Preserve the native-reference test's existing mode with a single composite opt-out. Its setup is otherwise unchanged: the current SDK tasks do not provide the required composite .r2r.wasm naming.
  • Document the default and include the per-assembly opt-out in composite validation errors.

Composite browser publishing was added in #134618. This change selects that existing mode by default; it does not enable ReadyToRun by default or resolve #132466.

Compatibility

Applications already opting into browser CoreCLR ReadyToRun will now publish a composite owner and component WebCIL stubs. They need WebCIL enabled and wasm-aware ReadyToRun SDK tasks. Existing validation and compatibility shims are retained.

To keep the previous per-assembly mode:

<PublishReadyToRun>true</PublishReadyToRun>
<PublishReadyToRunComposite>false</PublishReadyToRunComposite>

Mono defaults are unchanged. This does not change runtime-pack/CoreLib compilation or introduce a new public API.

Validation

export DOTNET_WASM_TOOL_CACHE_DIR="$PWD/.dotnet/wasm-tools"
./build.sh clr+libs+host+packs -os browser -arch wasm -c Release \
  /p:AotHostArchitecture=arm64 /p:AotHostOS=osx \
  /p:WasmToolCacheDir="$PWD/.dotnet/wasm-tools/"

Passed: zero errors, with four NU1902/NU1903 package-advisory warnings in the packaging-test dependencies.

./dotnet.sh build src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj -c Release \
  --disable-build-servers -nr:false \
  -p:TargetOS=browser -p:TargetArchitecture=wasm -p:RuntimeFlavor=CoreCLR \
  -p:TestUsingWorkloads=true -p:InstallWorkloadForTesting=false \
  -p:InstallChromeForTests=false -p:InstallFirefoxForTests=false -p:InstallV8ForTests=false \
  -p:WasmToolCacheDir="$PWD/.dotnet/wasm-tools/"

Passed: zero warnings and errors.

Focused Chrome execution using the built native SDK directly:

  • NativeLibraryTests.ProjectWithNativeReference_ReadyToRun, with its original setup: 1 passed, 0 failed, 0 skipped with the single composite opt-out. Removing only that opt-out made the same test fail the existing SDK naming guard: the SDK planned WasmBasicTestApp.r2r.dll instead of .r2r.wasm. No theory expansion, new assertions, or helper refactoring remains.
  • Current ReadyToRunTests composite publish and WebCIL-negative cases: 4 passed, 0 failed, 0 skipped. The local native-SDK import was restricted to explicit native relinks; the earlier ad-hoc runner had unintentionally enabled native relinking in a non-native case. The current run covers trimmed, untrimmed, native-relinked, repeated publish, and composite → per-assembly → composite switching.
  • The existing WebCIL-negative case also passed against the regenerated SDK package with the new opt-out hint. The naming-error hint was checked with an expected-failure MSBuild probe.

The workload-installed test lane could not be prepared because its manifest also requests unbuilt Mono runtime/AOT packs. The focused runs instead used the no-workload SDK with explicit imports of the built CoreCLR native SDK targets and the local Emscripten toolchain. This is local execution evidence, not a successful workload-installation or CI result.

XHARNESS_COMMAND=test-browser ./dotnet.sh build -t:Test \
  src/libraries/System.Memory/tests/System.Memory.Tests.csproj -c Release \
  --disable-build-servers -nr:false \
  -p:TargetOS=browser -p:TargetArchitecture=wasm -p:RuntimeFlavor=CoreCLR \
  -p:TestWasmReadyToRun=true -p:EnableAggressiveTrimming=true \
  -p:Scenario=WasmTestOnChrome -p:InstallChromeForTests=true \
  -p:WasmToolCacheDir="$PWD/.dotnet/wasm-tools/"

Passed: 52,252 passed, 0 failed, 2 skipped. No explicit composite property was supplied. The boot config identifies System.Memory.Tests.r2r.wasm as the composite owner, and its table size is nonzero.

Additional local checks:

  • 18 consumer-SDK property assertions passed, including WebAssembly/Blazor import chains, explicit opt-outs, R2R disabled, and unchanged Mono/ordinary SDK defaults. The new default assertions failed against the original source.
  • Five property checks against the actual library project and eight against the actual sample project passed.
  • SDK package contents were checked against current source, including the new defaults file and error hints.
  • git diff --check passed.

Review status

Independent Claude Sonnet 5.5 and Gemini 4 Argon reviews were completed. The sample opt-out forwarding gap and missing diagnostic guidance were fixed and reviewed by both models. The remaining human-review considerations are acceptance of the SDK compatibility/default-policy change, cross-platform CI, and comparative performance data before any performance claim.

Not run: the full Wasm.Build.Tests suite, cross-platform CI, or comparative WebAssembly performance measurements.

Note

This change and PR description were prepared with GitHub Copilot.

Preserve per-assembly opt-outs across consumer and in-tree builds, including nested sample publishes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519ad6c4-3702-45c6-bb99-0e2ad8573d04
@azure-pipelines

azure-pipelines Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
11 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 subscribers to this area: @dotnet/crossgen-contrib
See info in area-owners.md if you want to be subscribed.

@radekdoulik

Copy link
Copy Markdown
Member Author

/azp run runtime-coreclr crossgen2, runtime-coreclr crossgen2 outerloop, runtime-libraries-coreclr outerloop, runtime-wasm-libtests

Note

CI runs requested by the user via GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@radekdoulik

Copy link
Copy Markdown
Member Author

/azp run runtime-coreclr crossgen2, runtime-coreclr crossgen2 outerloop, runtime-libraries-coreclr outerloop, runtime-wasm-libtests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

@radekdoulik

Copy link
Copy Markdown
Member Author

/azp run runtime-libraries-coreclr outerloop

Note

Rerun requested by the user; this comment was generated by GitHub Copilot.

@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

@radekdoulik
radekdoulik marked this pull request as ready for review November 3, 2026 10:04
@radekdoulik

Copy link
Copy Markdown
Member Author

CI results for head 373571d (tested merge revision 88392cb):

Coverage Result Run
CoreCLR browser builds, WasmBuildTests, and R2R library smoke Passed 1659609
Native CoreCLR pri0 R2R tests All nine runtime-test configurations passed 1659613
Browser R2R libraries, full outerloop scope Build passed; 28/29 test batches passed 1659614, attempt 2
WASI R2R libraries, innerloop scope Build failed; tests not submitted 1659614, attempt 2
Native R2R outerloop Assertion/EventPipe failures also seen on main 1659615

The browser R2R retry completed its build without memory warnings, submitted the full test payload, and finished all 29 test batches. Both wasm failures in that retry have verified main matches:

  • Browser: System.Reflection.Metadata.ApplyUpdateTest.DeleteMemberTest fails with TypeLoadException, unable to load MetadataUpdateDeletedAttribute from System.Runtime.Loader, Version=12.0.0.0. Main build 1659716, in the Linux-only libraries outerloop pipeline, has the identical exception and matching stack in browser/CoreCLR R2R/Chrome. See test run 46212034, result 111816.
  • WASI: wasm-merge cannot parse Microsoft.Bcl.Cryptography.Tests.r2r.wasm: expected function end at offset 25705284. The same project, diagnostic, and offset occur in main build 1659717.

The native pri0 pipeline's red result is a separate macOS x64 host-library timezone failure matched to #136758, not a pri0 runtime-test failure. The native R2R outerloop signature assertion and composite EventPipe failure signatures have direct matches in main build 1659123. The regular runtime pipeline's Linux Mono WasmBuildTests provisioning failure also occurs on main build 1658830.

The expanded matrix is not fully green, and not every native host-library outerloop failure has been individually classified.

Note

This CI summary was prepared with GitHub Copilot.

@pavelsavara pavelsavara added arch-wasm WebAssembly architecture os-browser Browser variant of arch-wasm labels Nov 3, 2026
@pavelsavara pavelsavara added this to the 12.0.0 milestone Nov 3, 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.

Set PublishReadyToRunComposite directly in browser props whenever it is unset, without depending on runtime selection. Remove the early-target import, separate defaults file, and duplicate in-tree assignment.

Preserve explicit overrides and keep ReadyToRun itself opt-in.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519ad6c4-3702-45c6-bb99-0e2ad8573d04
@radekdoulik
radekdoulik requested a review from maraf November 3, 2026 15:46
@radekdoulik

Copy link
Copy Markdown
Member Author

/ba-g the failure is unrelated

@radekdoulik
radekdoulik merged commit 5da2b56 into dotnet:main Nov 3, 2026
47 of 49 checks passed
lewing added a commit that referenced this pull request Nov 7, 2026
…or composite R2R images (#137113)

Component assemblies of a multi-assembly composite R2R image never set
`ReadyToRunInfo::m_pLoadedImageBase`. Only the composite image's own
`ReadyToRunInfo` and single-assembly images set it
(`readytoruninfo.cpp`, non-component branch), and the loader heap
zero-fills, so the cDAC reads 0 for every component module. The native
runtime never reads this field; it uses `m_pComposite->GetImage()`.

Impact:
- **WebAssembly (browser/WASI):** the composite image's virtual-IP range
is registered under a component module. `FindVirtualIPRangeSection`
rejects ranges whose module has a null `LoadedImageBase`, so no
composite R2R code resolves and stack walks stop at the first explicit
Frame. Since composite became the browser default (#136486), every cDAC
walk of a `PublishReadyToRun` browser app stops this way.
- **All platforms:** `FrameHelpers.FindGCRefMap` computed `indirection -
0`, so GCRefMaps for `StubDispatchFrame`/`ExternalMethodFrame` import
cells in composite images were not found.

Fix: `Data.ReadyToRunInfo.LoadedImageBase` is now read through
`CompositeInfo`, the same pattern `EntryPointToMethodDescMap` already
uses. `CompositeInfo` points to itself for non-component images, so this
is a no-op there. There is no runtime or descriptor change, so it works
against runtimes that have already shipped. The
`ExecutionManager.md`/`StackWalk.md` docs are updated.

Validation:
- Composite cases added to
`GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape` and
`FindGCRefMap_ResolvesImportRecord`; both fail without the fix. `dotnet
test src/native/managed/cdac/tests/UnitTests`: 4046 passed.
- Live, browser-wasm (nightly 12.0.0-alpha.1.26555.106), nesm cDAC
stack-walk rig: a composite `PublishReadyToRun` app went from 2 frames
(InitialNativeContext + InlinedCallFrame) to a full walk. That walk has
22 frames, R2R and interpreted, through both InterpreterFrames, all
named. 24/24 pass across 6 modes (break, pause, JSImport, locals; R2R
and interpreter-only) on the composite and per-assembly publishes, nesm
workarounds on and off. Frame names match an independent static R2R
oracle; R2R locals read the expected source values.

The runtime side of the same composite image-base issue was fixed in
#131243; this is the cDAC counterpart.

> [!NOTE]
> This PR description was drafted with GitHub Copilot.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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 os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[browser][coreCLR] Enable R2R as default via PublishReadyToRun

4 participants