Skip to content

[browser][CoreCLR] Enable trimmed ReadyToRun library-test CI - #133656

Draft
lewing wants to merge 21 commits into
dotnet:mainfrom
lewing:lewing-wasm-r2r-library-tests
Draft

[browser][CoreCLR] Enable trimmed ReadyToRun library-test CI#133656
lewing wants to merge 21 commits into
dotnet:mainfrom
lewing:lewing-wasm-r2r-library-tests

Conversation

@lewing

@lewing lewing commented Sep 11, 2026

Copy link
Copy Markdown
Member

Summary

Addresses #133193 by making EnableAggressiveTrimming=true actually enable browser test trimming and adding separate LibraryTestsCoreCLR_R2R Chrome CI lanes. Uses the existing browser trimming configuration and descriptors; no Apple-mobile descriptor imports are added.

  • Introduce test-scoped TestWasmReadyToRun, avoiding global PublishReadyToRun=true reaching host-side build tools.
  • Pass the same R2R/trimming properties through build and Helix submission, with distinct test-run names. Existing interpreter lanes remain separate; new lanes select the supported inner-loop suites, not just smoke tests.
  • Set TEST_READY_TO_RUN_MODE=1 in browser R2R apps. Use PlatformDetection.IsWasmReadyToRun for shared browser/WASI R2R quarantines, preserving interpreter coverage.
  • Remove stale .ilproj Link metadata from the TestILAssembly reference so its DLL is staged under the correct VFS name.
  • Document the local workflow and retain WasmBuildNativeImplicitInReleaseConfiguration=false.

Prerequisites / draft status

Not ready to merge. The remaining unmerged prerequisite is:

Landed: #133639, the standalone CoreLib IsDynamicCodeCompiled trimming correction, is now on main at 1c1e32381c1. This branch merged that main revision in e220984f2bc, taking the complete upstream implementation, renamed substitution resource, and regression coverage. The substitution fix no longer appears in this PR's diff.

The library-test bring-up itself is commit 6ceb8f2d0cb. The main refresh also includes #133610's stack-trace fix. #133547 has not been integrated. The existing conformance quarantines remain; no test assertions were rewritten to avoid runtime defects.

Failure classification and quarantines

Category Handling
Actual trimming/configuration Enable ILLink for explicit EAT; consume the existing shared xUnit/TestUtilities roots. The CoreLib substitution defect (#133615) is fixed upstream by #133639.
R2R platform probes (#133614) Exclude only TestUtilities.dll from R2R. Libraries and test assemblies remain R2R; filesystem and networking tests remain enabled.
Array / closed static delegate (#133613, #133618) Method-level WASM-R2R ActiveIssues.
MethodBase.GetCurrentMethod conformance (#133617) Preserve original calls and assertions; use method-level ActiveIssues rather than rewriting tests to avoid the bug.
Async identities / EH / WhenAny (#133626, #132311, #133627) Method-level ActiveIssues; no class-wide exclusion or test-body changes. All 47 quarantined Tasks cases pass in the earlier trimmed interpreter control.

The WASM-wide predicate intentionally anticipates WASI R2R (#133265). The failure reproductions and execution results here are browser results, not a claim of WASI validation.

Local validation

macOS arm64 host, browser-wasm CoreCLR Release, Chromium 153, using the pinned publishing prerequisite.

After merging main at #133639, rebuilt browser CoreCLR, crossgen2, CoreLib, libraries, host, and packs, then rebuilt and reran the complete six-project R2R smoke command. The served native runtime asset byte-matched the rebuilt runtime pack.

Suite Passed Skipped Failed
System.Runtime 77,389 175 0
System.Net.Http.Functional 809 120 0
System.Net.WebSockets.Client 315 0 0
System.Runtime.InteropServices.JavaScript 474 2 0
System.Threading 549 87 0
System.Threading.Tasks 332 427 0

Before this main refresh, additional clean-output comparisons ran full UnmanagedMemoryStream, FileSystem, Cryptography, Data.Common, Globalization, and LINQ suites in both actual EAT and trimmed R2R, without the speculative Apple roots: 136,138 passed, 1,141 skipped, zero failed per configuration, with identical counts. Logs explicitly show PublishTrimmed=true and the effective descriptors. No replacement roots were required.

Also previously built a Helix test archive and ran its extracted contents successfully (UnmanagedMemoryStream, 32/32). Served HTTP library/test WASM assets byte-matched the Crossgen2 R2R outputs while TestUtilities had no native R2R image.

ActiveIssue-filtered cases are omitted from totals rather than counted as skipped. The full supported CI selection has not been validated remotely yet; this draft is for that next step. No performance claim is made.

Note

This PR description and changes were prepared with GitHub Copilot assistance.

pavelsavara and others added 19 commits September 8, 2026 13:18
The MetadataReaderProvider owns a memory-mapped section over the underlying stream. Leaving it to the
finalizer keeps the file mapped inside long-lived MSBuild task hosts, so a later writer targeting the
same path fails with "user-mapped section open". Observed as crossgen2 failing to rewrite an R2R image
that an earlier ConvertDllsToWebcil probe had opened.
ConvertDllsToWebcil may stage a prebuilt ReadyToRun image in place of converting IL. The guard compared
assembly versions, which almost never change between incremental builds, so a stale image compiled
against a previous IL set passed the check and was staged. With cross-module inlining every image in a
bundle belongs to one version bubble that the runtime validates by MVID at load, so that stale image is
a startup fail-fast rather than a graceful fallback.

Compare MVIDs instead, which turns the failure into a build-time fallback to IL conversion. Detect
webcil-in-wasm by content (the wasm magic) rather than by extension, because a prebuilt image may still
be named *.dll and PEReader would throw on it, returning null and silently bypassing the guard. The
"unreadable identity means accept" fallback is preserved.
…mbly SDK pack

The crossgen2 resolution override lived in WasmApp.InTree.props, so only in-tree builds could produce
per-app R2R; an out-of-tree app fell through to the base SDK, whose ReadyToRun pipeline predates wasm
support and emits composite images. Composite strips the assembly manifest, so the runtime fails
coreclr_initialize with 0x80131018 at startup.

Ship the wiring from the pack instead, in CoreCLR-only files so no Mono path gains a branch. The import
is gated on a props-time signal for an in-build crossgen2 (Crossgen2InBuildDir, or
Crossgen2SdkOverridePropsPath in-tree, since liveBuilds.targets sets the former at targets-time), and is
inert for stock consumers, which keep resolving crossgen2 through the base SDK.

The override probes both the raw in-build layout (crossgen2 at the root) and the shipped
Microsoft.NETCore.App.Crossgen2 pack layout (under tools/), and sets Crossgen2Tool directly, because the
base SDK resolver keys on ResolvedCrossgen2Pack which a standalone app does not populate for a local
build. It can be retired once a restorable wasm crossgen2 pack exists (dotnet/sdk#55785).
…d pipeline

Implements the two modes: a dev-loop build stages the prebuilt framework R2R images from the runtime
pack and ships the app as IL, while publish crossgens the whole closure per app, trimmed or untrimmed.

The main correctness problems addressed:

- Per-app R2R images are named <name>.wasm, but ComputeWasmPublishAssets classifies managed assemblies by
  the .dll extension, so the images were treated as native and leaked to the publish root, leaving the
  boot config with no coreAssembly. Restore the IL .dll in the publish list so ConvertDllsToWebcil stages
  the image from PrebuiltR2RDirectory. This has to happen in both the outer and nested passes: a native
  relink crossgens inside WasmNestedPublishApp, where ProcessPublishFilesForWasm is never scheduled, and
  _GatherWasmFilesToPublish filters to .dll, dropping every compiled assembly while exiting 0.

- ILLink stamps PostprocessAssembly on its own collection rather than ResolvedFileToPublish on the
  Blazor/static-web-assets route, so the mainline compile list was empty and crossgen2 never ran.

- A trimmed publish flow served the full copy-local set from the runtime pack mixed with the trimmed
  closure, which mixes version bubbles and lets an untrimmed assembly call a member ILLink removed from
  the trimmed framework. Restrict the served set to the linker output and repoint it there.

- Flag flips left derived outputs behind. Static web assets are content-fingerprinted, so a re-stage adds
  a new name beside the old file instead of replacing it, leaving two copies of an assembly from two
  different version bubbles. Record the mode and drop the derived outputs when it changes.

- Per-app crossgen inputs are deliberately conservative: cross-module inlining means any change must
  recompile every image, and stale images in obj/R2R are pruned.

Composite and non-wasm container formats are rejected with a comprehensible error instead of producing
images that fail at startup, and a missing crossgen2 is reported at the point of use.

PublishReadyToRun defaults to false; flipping it belongs to the codegen-quality work stream.
The four relink triggers keyed solely on IsBrowserWasmProject, which a Blazor app leaves unset because it
resolves the wasm RID late, so WasmBuildNative=true was a silent no-op there and the app shipped the
prebuilt dotnet.native.wasm from the runtime pack. OR in WasmBuildNative, which is unambiguous: this file
is imported only for CoreCLR browser-wasm apps. Kept as an OR so IsBrowserWasmProject, which also steers
ICU and tzdata skipping, is never forced on.

Fixes dotnet#133185
The nested publish evaluates ILLink.Tasks.csproj with different global properties, so MSBuild builds it a
second time and copies obj to bin over the assembly the outer pass has already loaded, failing with
MSB3027. The outer pass has built the task by the time the nested publish runs, so the reference is
redundant there as well as harmful.
Covers the dev-loop build (framework R2R staged from the runtime pack, no per-app crossgen), publish
trimmed and untrimmed (whole closure compiled per app), both with and without a native relink, and the
disabled case. Each publish case drives Home, Counter and Weather in a real browser, which is what
distinguishes a bundle that boots from one that merely looks staged.

The assertions target failures seen during bring-up that still exit 0: assemblies missing from the
staged set relative to the linker closure, duplicate fingerprinted copies of one assembly, managed
assemblies leaking outside _framework, and per-app crossgen running (or not) for the mode.

Adds the Weather page that the nav menu of the test app already linked to, and ships the in-build
crossgen2 plus the wasm-aware Crossgen2Tasks shim as Helix correlation payload so the tests can resolve
them there.
Gives the R2R pipeline a library-test vehicle: this suite exercises the trimmed publish flow, where the
served bundle must be exactly the linker closure staged as per-app R2R images. CoreCLR only; Mono is
unaffected. tests.browser.targets already implies PublishTrimmed from PublishReadyToRun.
… pack on Helix

Fixes the gap CI uncovered: the no-workload Wasm.Build.Tests Helix leg has no in-build crossgen2, so the
per-app publish tests aborted with a NullReferenceException from the base SDK PrepareForReadyToRunCompilation
task (empty Crossgen2Tool). The SDK does restore a crossgen2 pack when PublishReadyToRun is set; the failure
was that the override ignored it.

- The ResolveReadyToRunCompilers override no longer shadows the base/shim resolver into an empty tool. It uses
  the in-build crossgen2 when present (in-tree / dev builds) and otherwise resolves the tool from the
  SDK-restored crossgen2 pack (@(ResolvedCrossgen2Pack)), so @(Crossgen2Tool) is never left empty when a tool
  is available.

- The wasm-aware Crossgen2Tasks shim (the wasm-container crossgen tasks, which the base SDK still lacks) now
  ships as a Helix correlation payload gated on its own path rather than on the crossgen2 bin dir, which the
  WBT test leg does not carry, so the shim actually reaches the worker.

- GetR2RBuildArgs passes Crossgen2InBuildDir and the shim override paths only when each exists under BASE_DIR.
  On the no-workload leg crossgen2 is resolved from the SDK pack, so passing a non-existent Crossgen2InBuildDir
  would otherwise break the call-helpers generator.

- The Wasm.Build.Tests CoreCLR project setup pins KnownCrossgen2Pack to the locally built pack version, so the
  SDK restores the wasm-capable crossgen2 pack from the local feed rather than a default one.

Validated in-tree: console-node ReadyToRun publish builds and runs under node.
- ResolveReadyToRunCompilers override: drop the last-resort Crossgen2Path that named a possibly non-existent
  executable; when the in-build dir has no crossgen2 the tool is left to the SDK-pack fallback (and ultimately
  the actionable _WasmCoreClrValidateReadyToRun error) instead of failing with "file not found".
- Use Update="@(...)" on the CopyToOutputDirectory metadata item groups so the metadata is stamped on the
  existing items rather than relying on bare metadata-only elements.
- Revert the WasmBuildNative relink trigger OR: the relink is driven by IsBrowserWasmProject only, as before.
  dotnet#133185 does not reproduce on current SDKs and the OR was defensive.
- Narrow the stale-mode served-assets cleanup to the fingerprinted *.wasm/*.dll we stage, so the SDK never
  recursively removes app-authored content under the served directory.
- Quote the crossgen2 override path arguments passed by the Wasm.Build.Tests ReadyToRun opt-in.
_WasmCoreClrSuppressNestedPublishAssetCopy and _WasmCoreClrRestoreCopyToOutputDirectory
only do anything when _WasmFrameworkCopyToOutputDirectory=PreserveNewest, which is set
exclusively by eng/testing/tests.browser.targets so the xunit runner gets framework
assets in bin/. For a real app the default is Never, making both a no-op, so they move
out of the shipped WebAssembly SDK into the test infra, gated on RuntimeFlavor=CoreCLR.

Also drop _WasmCoreClrInvalidateStaleModeOutputs, which deleted stale derived outputs on
a mode flip. Incremental mode-flip cleanup will be reimplemented later without deleting
files; every test/CI matrix cleans obj per case, so nothing exercised it.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Only embed the JIT-specific IsDynamicCodeCompiled substitution when FeatureDynamicCodeCompiled is enabled. Interpreter-only CoreCLR must retain its false getter after trimming.

Fixes dotnet#133615

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Honor browser aggressive trimming and add a test-scoped ReadyToRun switch with matching build and Helix CI lanes. Preserve existing browser descriptors instead of importing Apple-only roots.

Keep TestUtilities interpreted for the tracked platform-probe issue, preserve original conformance assertions, and quarantine known WebAssembly R2R failures with a shared browser/WASI predicate.

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

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
10 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 dotnet-policy-service Bot added the linkable-framework Issues associated with delivering a linker friendly framework label Sep 11, 2026
@lewing
lewing requested review from maraf, pavelsavara and radekdoulik and a lite review from Copilot September 11, 2026 03:02
@lewing lewing added arch-wasm WebAssembly architecture and removed area-Build-mono labels Sep 11, 2026
@lewing lewing added area-Infrastructure-libraries os-browser Browser variant of arch-wasm labels Sep 11, 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.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Integrate dotnet#133639 at 1c1e323, including its renamed substitution resource and regression coverage. Resolve the existing local substitution gate to the landed upstream implementation.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved configuration, trimmed-closure assertion, and MVID image-validation findings remain.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 High severity · 1 Medium severity

New issues introduced by this change (2)
Severity Finding
High severity src/​tasks/​Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/​ConvertDllsToWebCil.cs — Reject unreadable prebuilt images before staging
Medium severity src/​mono/​nuget/​Microsoft.NET.Sdk.WebAssembly.Pack/​build/​Microsoft.NET.Sdk.WebAssembly.Browser.props — Gate the CoreCLR crossgen override away from Mono builds
What changed in this PR

Enables trimmed CoreCLR browser library-test CI with ReadyToRun while preserving separate interpreter coverage.

Changes:

  • Adds scoped trimming/R2R properties and dedicated CI lanes.
  • Adds CoreCLR browser publishing, Crossgen2 resolution, WebCIL staging, and MVID validation.
  • Adds R2R test coverage, quarantines, Helix propagation, and workflow documentation.
File Summary
src/​tasks/​Microsoft.NET.WebAssembly.Webcil/​WebcilReader.cs Corrects metadata-reader disposal.
src/​tasks/​Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/​ConvertDllsToWebCil.cs Validates and stages R2R WebCIL images.
src/​mono/​wasm/​Wasm.Build.Tests/​WebcilInWasmSizesTests.cs Covers WebCIL/R2R staging behavior.
src/​mono/​wasm/​Wasm.Build.Tests/​Templates/​WasmTemplateTestsBase.cs Pins Crossgen2 pack versions.
src/​mono/​wasm/​Wasm.Build.Tests/​ReadyToRunTests.cs Tests R2R publishing and trimmed closure behavior.
src/​mono/​wasm/​Wasm.Build.Tests/​Common/​EnvironmentVariables.cs Adds test environment configuration.
src/​mono/​wasm/​testassets/​BlazorBasicTestApp/​App/​Pages/​Weather.razor Supports browser interaction testing.
src/​mono/​sample/​wasm/​Directory.Build.props Updates sample build properties.
src/​mono/​nuget/​Microsoft.NET.Sdk.WebAssembly.Pack/​build/​Microsoft.NET.Sdk.WebAssembly.Browser.props Activates CoreCLR R2R wiring.
src/​mono/​nuget/​Microsoft.NET.Sdk.WebAssembly.Pack/​build/​Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets Implements CoreCLR browser R2R publishing.
src/​mono/​nuget/​Microsoft.NET.Sdk.WebAssembly.Pack/​build/​Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.ReadyToRun.targets Resolves browser-compatible Crossgen2 tooling.
src/​mono/​nuget/​Microsoft.NET.Sdk.WebAssembly.Pack/​build/​Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props Imports CoreCLR R2R configuration.
src/​mono/​browser/​build/​WasmApp.ReadyToRun.targets Updates browser R2R target wiring.
src/​mono/​browser/​build/​WasmApp.InTree.props Updates in-tree browser build properties.
src/​libraries/​System.Runtime/​tests/​System.Threading.Tasks.Tests/​System.Runtime.CompilerServices/​AsyncProfilerV2Tests.cs Adds targeted R2R quarantines.
src/​libraries/​System.Runtime/​tests/​System.Threading.Tasks.Tests/​System.Runtime.CompilerServices/​AsyncProfilerV1Tests.cs Adds targeted R2R quarantines.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System/​Runtime/​CompilerServices/​MethodImplAttributeTests.cs Adds R2R-specific test handling.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System/​Reflection/​MethodBaseTests.cs Adds R2R-specific test handling.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System/​Reflection/​CustomAttributeDataTests.cs Adds R2R-specific test handling.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System/​ExceptionTests.cs Adds R2R-specific test handling.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System/​DelegateTests.cs Adds R2R-specific test handling.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System/​ArrayTests.cs Adds R2R-specific test handling.
src/​libraries/​System.Runtime/​tests/​System.Runtime.Tests/​System.Runtime.Tests.csproj Updates trimming and assembly staging metadata.
src/​libraries/​System.Runtime.InteropServices.JavaScript/​tests/​System.Runtime.InteropServices.JavaScript.UnitTests/​System.Runtime.InteropServices.JavaScript.Tests.csproj Configures JavaScript interop test publishing.
src/​libraries/​sendtohelix-browser.targets Propagates browser test settings.
src/​libraries/​Directory.Build.props Adds test-scoped R2R enablement.
src/​libraries/​Common/​tests/​TestUtilities/​System/​PlatformDetection.cs Adds WASM R2R detection.
src/​coreclr/​System.Private.CoreLib/​System.Private.CoreLib.csproj Updates CoreLib trimming configuration.
eng/​testing/​tests.browser.targets Configures browser trimming and R2R settings.
eng/​pipelines/​runtime.yml Adds standard browser R2R coverage.
eng/​pipelines/​extra-platforms/​runtime-extra-platforms-wasm.yml Adds extra-platform R2R coverage.
eng/​pipelines/​common/​templates/​wasm-coreclr-library-tests.yml Propagates R2R and trimming parameters.
eng/​liveILLink.targets Enables browser test trimming configuration.
docs/​workflow/​testing/​libraries/​testing-wasm.md Documents local trimmed R2R testing.

Comment on lines +277 to 278
if (prebuiltMvid is null || candidateMvid.Equals(prebuiltMvid.Value))
return true;
in-tree signal is Crossgen2SdkOverridePropsPath (set props-time in the repo Directory.Build.props).
Inert for stock consumers (neither set), which resolve crossgen2 through the base SDK. -->
<Import Project="$(MSBuildThisFileDirectory)Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.props"
Condition="'$(Crossgen2InBuildDir)' != '' or '$(Crossgen2SdkOverridePropsPath)' != ''" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Infrastructure-libraries linkable-framework Issues associated with delivering a linker friendly framework os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants