Enable ReadyToRun for CoreCLR browser-wasm - #132339
Draft
pavelsavara wants to merge 3 commits into
Draft
Conversation
ResolveReadyToRunCompilers: add wasm architecture and browser/wasi OS so the SDK can resolve the crossgen2 pack for wasm. Dual-install clrjit_universal_wasm_* into the alljits component so the shipped Microsoft.NETCore.App.Crossgen2.<hostRID> pack carries the wasm cross-JIT (crossgen2 v6+ auto-loads it from tools/). Assisted-by: GitHub Copilot
… pack Crossgen each shared-framework assembly to a webcil-in-wasm R2R image under runtimes/browser-wasm/native/r2r/, keeping the IL in lib/ (the CoreLib model generalized to the whole framework). CoreLib.wasm comes from the coreclr build. WasmFrameworkR2RSubset limits the set for bring-up. Nothing consumes these yet; the SDK integration is a follow-up commit. See dotnet#121257. Assisted-by: GitHub Copilot
…ublish ConvertDllsToWebcil gains PrebuiltR2RDirectories to stage prebuilt/per-app R2R webcils in place of IL. New Browser.CoreCLR.targets (imported only for UseMonoRuntime=false) defaults R2R on, forces wasm container format + non-composite, prunes raw pack images, feeds the trimmed closure to crossgen2, and orders R2R before the wasm publish. In-tree hooks override ResolveReadyToRunCompilers to the in-build crossgen2. Design and SWA-vs-iOS notes in PR.md. Assisted-by: GitHub Copilot
|
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. |
Contributor
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end build/pack/SDK plumbing to enable ReadyToRun (R2R) for the CoreCLR browser-wasm runtime flavor by producing per-assembly “webcil-in-wasm” R2R images and teaching the WebAssembly SDK pipeline to stage/consume them through the existing Static Web Assets + ConvertDllsToWebcil flow.
Changes:
- Extend
ConvertDllsToWebciland the WebAssembly Browser targets to optionally probe and stage prebuilt R2R images (pack-provided or per-app crossgen output) instead of converting IL. - Update Crossgen2Tasks R2R compiler resolution logic to recognize
browser/wasitarget OS andwasmarchitecture. - Add new CoreCLR-specific WebAssembly SDK targets + runtime-pack producer wiring (including packaging of wasm cross-JIT and producing
native/r2r/*.wasmpayloads).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs | Adds PrebuiltR2RDirectories probing and staging of prebuilt R2R webcil-in-wasm images. |
| src/tasks/Crossgen2Tasks/RunReadyToRunCompiler.cs | Introduces a new Crossgen2 task parameter for composite extra args (currently unused). |
| src/tasks/Crossgen2Tasks/ResolveReadyToRunCompilers.cs | Adds browser/wasi OS and wasm architecture support for Crossgen2 pack resolution. |
| src/mono/sample/wasm/console-node/Wasm.Console.Node.Sample.csproj | Sets PublishReadyToRun in the sample project. |
| src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj | Adds a commented-out PublishReadyToRun line in the example block. |
| src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets | Wires PrebuiltR2RDirectories into ConvertDllsToWebcil and imports CoreCLR-specific targets when UseMonoRuntime == false. |
| src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets | New: CoreCLR-only R2R defaults, selection of probe dirs, pruning raw pack R2R from outputs, and publish ordering hooks. |
| src/mono/browser/build/WasmApp.ReadyToRun.targets | New: in-tree override of ResolveReadyToRunCompilers to use in-build crossgen2. |
| src/mono/browser/build/WasmApp.InTree.props | Adds props-time hooks to import the in-tree R2R override for CoreCLR browser builds. |
| src/mono/browser/build/BrowserWasmApp.CoreCLR.targets | Remaps managed assembly scanning to linker output when R2R webcil-in-wasm images are present. |
| src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj | Adds producer-side crossgen loop to emit and pack native/r2r/*.wasm framework images for browser/wasi. |
| src/coreclr/jit/CMakeLists.txt | Ensures the universal wasm JIT is installed into alljits (packaging for crossgen2). |
Comment on lines
23
to
27
| public bool ShowCompilerWarnings { get; set; } | ||
| public bool UseCrossgen2 { get; set; } | ||
| public string Crossgen2ExtraCommandLineArgs { get; set; } | ||
| public string Crossgen2CompositeExtraCommandLineArgs { get; set; } | ||
| public ITaskItem[] Crossgen2PgoFiles { get; set; } |
Comment on lines
+19
to
+24
| <PropertyGroup Condition="'$(PublishReadyToRun)' == 'true'"> | ||
| <!-- Force over SDK defaults: net11+ defaults ContainerFormat=pe (unloadable on wasm); staging requires non-composite. --> | ||
| <PublishReadyToRunContainerFormat Condition="'$(PublishReadyToRunContainerFormat)' != 'wasm'">wasm</PublishReadyToRunContainerFormat> | ||
| <PublishReadyToRunComposite Condition="'$(PublishReadyToRunComposite)' != 'false'">false</PublishReadyToRunComposite> | ||
| <!-- NYI/SIMD methods crossgen2 can't emit into R2R fall back to the runtime JIT. --> | ||
| <PublishReadyToRunCrossgen2ExtraArgs>$(PublishReadyToRunCrossgen2ExtraArgs);--codegenopt:JitWasmNyiToR2RUnsupported=1;--codegenopt:JitWasmSimdNyiToR2RUnsupported=1</PublishReadyToRunCrossgen2ExtraArgs> |
Comment on lines
+91
to
+106
| <PropertyGroup> | ||
| <_WasmCoreLibIL Condition="Exists('$(CoreCLRArtifactsPath)System.Private.CoreLib.dll')">$(CoreCLRArtifactsPath)System.Private.CoreLib.dll</_WasmCoreLibIL> | ||
| <_WasmCoreLibIL Condition="'$(_WasmCoreLibIL)' == '' and Exists('$(CoreCLRArtifactsPath)IL\System.Private.CoreLib.dll')">$(CoreCLRArtifactsPath)IL\System.Private.CoreLib.dll</_WasmCoreLibIL> | ||
| <_WasmCrossgen2Exe>$([MSBuild]::NormalizePath('$(Crossgen2InBuildDir)', 'crossgen2$(ExeSuffix)'))</_WasmCrossgen2Exe> | ||
| </PropertyGroup> | ||
| </Target> | ||
|
|
||
| <Target Name="_CrossgenBrowserFrameworkR2R" | ||
| Condition="'$(WasmEnableFrameworkR2R)' == 'true'" | ||
| DependsOnTargets="_ComputeBrowserFrameworkR2RInputs" | ||
| Inputs="@(_WasmFrameworkR2RInput);$(_WasmCoreLibIL)" | ||
| Outputs="@(_WasmFrameworkR2RInput->'%(OutputWasm)')"> | ||
| <MakeDir Directories="$(_WasmFrameworkR2RDir)" /> | ||
| <Message Importance="High" Text="Crossgen R2R (wasm): %(_WasmFrameworkR2RInput.FileName)" /> | ||
| <Exec Condition="Exists('$(_WasmCrossgen2Exe)')" | ||
| Command=""$(_WasmCrossgen2Exe)" -o:"%(_WasmFrameworkR2RInput.OutputWasm)" -r:"$(LibrariesSharedFrameworkBinArtifactsPath)*.dll" -r:"$(_WasmCoreLibIL)" --targetarch:$(TargetArchitecture) --targetos:$(TargetOS) --obj-format:wasm --codegenopt:JitWasmNyiToR2RUnsupported=1 --codegenopt:JitWasmSimdNyiToR2RUnsupported=1 -O "%(_WasmFrameworkR2RInput.FullPath)"" /> |
This was referenced Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Enables ReadyToRun (R2R) for the CoreCLR runtime flavor on
browser-wasm(experimental in .NET 11).R2R images are webcil-in-wasm modules: a wasm module whose data carries the assembly's ECMA metadata
(the per-assembly manifest) plus the crossgen2-produced R2R native code. Non-composite (per-assembly)
only; composite is out of scope but nothing here precludes it.
Related dotnet/sdk#55785
Commits (incremental review)
The change is split into three commits so the first two can stand alone as pre-step PRs if preferred:
jit/CMakeLists.txt+Crossgen2Tasks. Tooling enablement, no app-facing behavior.Two R2R flavors, one staging path
Both flavors are consumed by the existing
ConvertDllsToWebciltask through a newPrebuiltR2RDirectoriesparameter, so R2R images ride the same Static Web Assets (SWA) pipeline as aplain webcil — they are just a larger
_framework/<name>.wasm.dotnet build, and untrimmedpublishnative/r2r/*.wasm(all 181 shared-framework assemblies, shipped in the pack)publishcrossgen2over the trimmed ILLink closure →$(IntermediateOutputPath)R2R/*.dllThe prebuilt images are untrimmed, so they may only substitute for an untrimmed input. A
buildisnever trimmed, so it always uses the pack images. A trimmed
publishmust crossgen the trimmedclosure per-app (an untrimmed pack image can reference types the trimmer removed).
PublishReadyToRundefaults totruefor CoreCLR browser (mirrorsAppleBuild.props); set itfalsefor a pure-IL webcil app, byte-identical to today.Producer — runtime pack
Microsoft.NETCore.App.Runtime.CoreCLR.sfxprojcrossgens each shared-framework assembly to awebcil-in-wasm R2R image and packs it under
runtimes/browser-wasm/native/r2r/, keeping the ILlib/net11.0/*.dllin place for trimming and reference resolution. This is the CoreLib model(
crossgen-corelib.proj) generalized to the whole framework.lib/*.dllwith the R2R image and dropsthe IL, which trimming and per-app crossgen still need.
native/r2r/is a dedicated subfolder so the raw images never collide withdotnet.native.wasmora served
_frameworkwebcil, and so the prune hooks below match on one path prefix (no name-baseddotnet.nativespecial-casing).LibrariesRuntimeFiles ... IsNative="true" NativeSubDirectory="r2r";Microsoft.NETCore.App.Runtime.propsalready maps%(NativeSubDirectory)tonative/<sub>, so no packaging-layer change is needed.System.Private.CoreLib.wasmis produced by the coreclr build (crossgen-corelib), not by thisloop (CoreLib is not in the libraries shared-framework dir), so it is picked up from
$(CoreCLRArtifactsPath)explicitly.PlatformManifestFileEntry(required for RuntimePack file validation).WasmFrameworkR2RSubset(;-delimited assembly names) crossgens only a subset, for bring-up.(
$(MicrosoftNetCoreAppRuntimePackNativeDir)r2r/) because that layout is assembled bybinplace +
libs.pretest, not from the pack nupkg, so local dev/test builds need them copied in.(
--codegenopt:JitWasm{,Simd}NyiToR2RUnsupported=1).Measured (Release): 181 images, 90.6 MB R2R vs 15.4 MB IL (CoreLib 29 MB dominates). The crossgen
loop is 181 serial
Execprocesses; a parallel wrapper is a possible follow-up.Consumer — SDK targets
All CoreCLR logic lives in a new
Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets, imported at the end ofMicrosoft.NET.Sdk.WebAssembly.Browser.targetswithCondition="'$(UseMonoRuntime)' == 'false'", soMono is never touched.
.targets, not.props. The shipped SDK defaultsUseMonoRuntime=trueand importsthe WebAssembly
.propsbefore the project body setsUseMonoRuntime=false, so any.propscondition on
UseMonoRuntimewould be permanently dead. The property defaults land at targets time,which is still before
ProcessFrameworkReferences(a target) emits the crossgen2PackageDownload.'$(UseMonoRuntime)' == 'false'(not!= 'true') because Mono browser appscommonly leave
UseMonoRuntimeunset.Property defaults (when
PublishReadyToRun):PublishReadyToRunContainerFormat=wasm(the net11+ SDKdefaults it to
pe, which the wasm runtime can't load),PublishReadyToRunComposite=false, and theNYI codegen args. The residual edit to the shared
Browser.targetsis the one import plusPrebuiltR2RDirectories="…"on the twoConvertDllsToWebcilcalls (empty and inert for Mono).Targets in
CoreCLR.targets:_WasmCoreClrSelectR2RDirectories— computes the build vs publish probe directories (separateproperties: build always uses the pack dir; publish uses the per-app crossgen dir when trimming,
else the pack dir).
_WasmPackR2RDiris computed at target time because it depends on_RuntimePackNativeDir, which_ResolveGlobalizationConfigurationonly sets at target time._WasmCoreClrPrunePackR2RFromBuild/_WasmCoreClrPruneR2RFromPublish— remove the raw packnative/r2r/*.wasmfromReferenceCopyLocalPaths/ResolvedFileToPublishso the 90 MB untrimmedclosure never lands loose in
bin/or the publish root. The managed.dllcandidate stays;ConvertDllsToWebcilstages the matching R2R image in its place._WasmFeedReadyToRunCompileList/_WasmEnsureReadyToRunBeforePublish— see "SWA vs iOS" below.Task —
ConvertDllsToWebcilNew optional
string[] PrebuiltR2RDirectories. For a managed, non-culture.dllcandidate whoseR2RWebcilPathmetadata is empty, probe each directory for a matching image and stage (copy) thefirst hit instead of converting the IL. Existing
R2RWebcilPathmetadata still wins.Probe order is
.dllbefore.wasm: per-app crossgen (--obj-format:wasm --out:<name>.dll)writes the R2R image to
<name>.dlland can leave a same-named<name>.wasmthat is not the R2Rimage. Pack images are named
<name>.wasmwith no sibling.dll, so they still resolve.SWA vs iOS — the practical difference
The crossgen step is identical to iOS: the wasm publish reuses the stock SDK
_PrepareForReadyToRunCompilation → _CreateR2RImages → CreateReadyToRunImageschain. The divergenceis entirely in how the produced R2R images reach the output.
CreateReadyToRunImagesswaps them intoResolvedFileToPublish, copied as native filesResolvedFileToPublish, fed toConvertDllsToWebcil→DefineStaticWebAssets_CreateR2RSymbols), Mach-O link (_LinkReadyToRunMachO), apphost/.appTelemetry is preserved. Because the wasm publish reuses
CreateReadyToRunImages, the SDK'sAllowEmptyTelemetry EventName="ReadyToRun"(crossgen2 pack version, container format, compile-listcount, failure count) still fires, as do the
PublishReadyToRun/PublishReadyToRunCompositetarget-framework telemetry properties and the crossgen failure→
NETSdkErrorpath.The cost of the SWA route is two hooks that re-add what the standard
ResolvedFileToPublish-based pathgives for free, because on the Blazor/wasm route managed assemblies flow ILLink → SWA and never
surface as
ResolvedFileToPublishwithPostprocessAssembly=true:_WasmFeedReadyToRunCompileList— the mainline builds the R2R compile list fromPostprocessAssembly=trueitems onResolvedFileToPublish. On the SWA route ILLink stamps that onits own collection instead, so the mainline list is empty and crossgen2 never runs. This target
bridges the trimmed IL from the linker output dir into
ResolvedFileToPublishas the compile +reference set, and clears any stale base-SDK R2R mark on the untrimmed runtime-pack copy (e.g.
CoreLib under
native/) so crossgen2 can resolve against the trimmed closure._WasmEnsureReadyToRunBeforePublish—ProcessPublishFilesForWasmrunsAfterTargets="ILLink",which in a Blazor publish schedules it before
CreateReadyToRunImages; without forcing crossgenfirst, the publish would convert trimmed IL and the R2R images (produced later) would never ship.
In-tree
WasmApp.InTree.propskeeps only the two props-time hooks:AfterMicrosoftNETSdkTargetsappendsWasmApp.ReadyToRun.targets(which overrides theResolveReadyToRunCompilerstarget to use thein-build crossgen2, mirroring
AppleBuild.ReadyToRun.targets) ahead of the SDK targets import, plusthe
Crossgen2SdkOverridePropsPathimport. Both inert unlessPublishReadyToRun.BrowserWasmApp.CoreCLR.targetsremaps_WasmManagedAssembliesto the trimmed IL from$(IntermediateLinkDir): with R2R the published.dllare webcil-in-wasm images thatMetadataLoadContext(inManagedToNativeGenerator) cannot parse, so the IL closure is scannedinstead.
JIT packaging
jit/CMakeLists.txtdual-installsclrjit_universal_wasm_*into bothwasmjitandalljits, so theshipped
Microsoft.NETCore.App.Crossgen2.<hostRID>pack carries the wasm cross-JIT. crossgen2 v6+auto-loads
clrjit_universal_wasm_<host>from its owntools/dir (no--jitpath).Related dotnet/sdk change
ResolveReadyToRunCompilers(kept in sync insrc/tasks/Crossgen2Tasks/) gainswasmarchitectureand
browser/wasiOS support so the shipped SDK can resolve the crossgen2 pack for wasm. Thematching dotnet/sdk PR also stops forcing
--compositefor thewasmcontainer format (that produceda composite-of-one image with no per-assembly manifest → runtime
COR_E_BADIMAGEFORMAT).Validation
src/mono/sample/wasm/browserbefore/after and diff the executedtargets and the
_frameworklisting.dotnet build(pack R2R staged) and trimmeddotnet publish(per-app crossgen);confirm
_framework/<name>.wasmsizes match the source and nothing lands loose.PublishReadyToRun=false: pure-IL webcil, byte-identical to today.present); the app boots and executes managed code.
Note
This pull request was drafted with assistance from GitHub Copilot.