[browser][coreCLR] browserhost to load R2R - #129634
Conversation
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
There was a problem hiding this comment.
Pull request overview
This PR extends the WebAssembly build/publish pipeline to support ReadyToRun (R2R) “webcil-in-wasm” assemblies by staging prebuilt R2R .wasm images, emitting webcil payload/table sizing into the boot config, and updating the JS loader/host to instantiate webcil modules using those sizes (enabling streaming instantiation and avoiding runtime-side wasm parsing).
Changes:
- Add an MSBuild path to crossgen selected assemblies to R2R webcil-in-wasm and feed those artifacts into webcil conversion/staging.
- Emit
payloadSize(andtableSizefor R2R) into the boot config assets so the loader can instantiate without callinggetWebcilSize/ parsing wasm. - Update the JS loader/host surface and typings to pass these sizes through to
instantiateWebcilModule.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs | Plumbs webcil size metadata into boot config generation and passes it to asset transformation. |
| src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs | Adds optional staging of prebuilt R2R .wasm replacements and emits a WebcilSizes output by parsing wasm. |
| src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs | Extends boot asset schema with tableSize/payloadSize fields. |
| src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs | Writes payloadSize/tableSize onto boot assets during resources→assets transformation. |
| src/native/libs/Common/JavaScript/types/public-api.ts | Exposes tableSize/payloadSize on AssemblyAsset in public TS types. |
| src/native/libs/Common/JavaScript/types/internal.ts | Adds internal asset fields for tableSize/payloadSize. |
| src/native/libs/Common/JavaScript/types/ems-ambient.ts | Extends ambient emscripten symbol typing for wasm exports needed by R2R webcil imports. |
| src/native/libs/Common/JavaScript/loader/dotnet.d.ts | Updates loader .d.ts for AssemblyAsset to include tableSize/payloadSize. |
| src/native/libs/Common/JavaScript/loader/assets.ts | Passes tableSize/payloadSize to browser-host instantiateWebcilModule. |
| src/native/libs/Common/JavaScript/host/assets.ts | Reworks instantiateWebcilModule to allocate payload and instantiate webcil wasm (streaming when possible) using boot-config sizes. |
| src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets | Wires R2R candidates into ConvertDllsToWebcil, propagates WebcilSizes, and adds a _WasmCrossgenReadyToRunAssemblies target. |
| src/mono/browser/build/WasmApp.InTree.props | Defines in-tree crossgen2/jit/reference paths consumed by the new R2R target. |
| src/mono/sample/wasm/browser-R2R/Wasm.Browser.R2R.csproj | Adds a new sample that opts its app assembly into R2R cross-compilation. |
| src/mono/sample/wasm/browser-R2R/Program.cs | Sample app entrypoint. |
| src/mono/sample/wasm/browser-R2R/wwwroot/index.html | Sample web page host. |
| src/mono/sample/wasm/browser-R2R/wwwroot/main.js | Sample boot/run script using dotnet.js. |
| // Key by the logical assembly name (".dll"), not the produced ".wasm" file name: the boot | ||
| // config lists webcil assemblies under their logical ".dll" name, and GenerateWasmBootJson | ||
| // looks these sizes up by that name (resourceName). Using ".wasm" here would never match. | ||
| // Keying by ".dll" also avoids colliding with same-stem assets (e.g. a "X.pdb" never matches | ||
| // "X.dll"). | ||
| string fileName = Path.ChangeExtension(Path.GetFileName(webcilPath), ".dll"); | ||
| string key = string.IsNullOrEmpty(culture) ? fileName : culture + "/" + fileName; | ||
| var item = new TaskItem(key); |
| export async function instantiateWebcilModule(webcilPromise: Promise<Response>, memory: WebAssembly.Memory, virtualPath: string, tableSize?: number, payloadSize?: number): Promise<void> { | ||
| // The boot config carries payloadSize for every webcil asset (and tableSize for R2R images), so | ||
| // the loader never buffers the bytes, parses the data section or calls getWebcilSize. Assets | ||
| // without a tableSize are plain (Webcil wrapper version 0) images. | ||
| if (typeof payloadSize !== "number" || payloadSize === 0) { | ||
| throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`); | ||
| } |
| <ConvertDllsToWebcil | ||
| Candidates="@(_WasmDllBuildCandidates)" | ||
| IntermediateOutputPath="$(_WasmBuildTmpWebcilPath)" | ||
| OutputPath="$(_WasmBuildWebcilPath)" | ||
| IsEnabled="$(_WasmEnableWebcil)" | ||
| WebcilVersion="$(_WasmWebcilVersion)" | ||
| R2RWebcilCandidates="@(WasmR2RWebcilCandidate)"> | ||
| <Output TaskParameter="FileWrites" ItemName="FileWrites" /> | ||
| <Output TaskParameter="FileWrites" ItemName="_WasmConvertedWebcilOutputs" /> | ||
| <Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" /> |
| /// <summary> | ||
| /// Payload/table sizes for each webcil (keyed by the logical assembly name, e.g. "App.dll"), | ||
| /// produced by ConvertDllsToWebcil. Emitted into the boot config so the runtime loader doesn't | ||
| /// parse the wasm or call getWebcilSize. | ||
| /// </summary> | ||
| public ITaskItem[] WebcilSizes { get; set; } |
| /// <summary> | ||
| /// Payload/table sizes for each produced webcil, keyed by the logical assembly name as it | ||
| /// appears in the boot config: the file name (e.g. "System.Console.dll"), or | ||
| /// "{culture}/{name}.dll" for satellite assemblies so that same-named satellites in different | ||
| /// cultures don't collide. Lets the boot config carry the sizes so the runtime loader doesn't | ||
| /// buffer/parse the wasm. PayloadSize is set for every webcil; TableSize is non-zero only for | ||
| /// R2R images. | ||
| /// </summary> |
| extern "C" UINT_PTR STDCALL GetCurrentIP(void) | ||
| { | ||
| PORTABILITY_ASSERT("GetCurrentIP is not implemented on wasm"); | ||
| // WASM-TODO: Implement this function to return the current instruction pointer in the interpreter. | ||
| return 0; |
| int want = dataLength >= 8 ? 8 : 4; | ||
| byte[] sizes = new byte[8]; | ||
| if (!TryFill(fs, sizes, want)) | ||
| { | ||
| failureReason = "data segment 0 was truncated before the sizes could be read"; | ||
| return false; | ||
| } | ||
|
|
||
| payloadSize = (int)ReadUInt32LE(sizes, 0); | ||
| tableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0; | ||
| return true; |
| function displayMeaning(meaning) { | ||
| console.log(`Meaning of life is ${meaning}`); | ||
| document.getElementById("out").innerHTML = `${meaning}`; | ||
| } | ||
|
|
||
| function delay(ms) { | ||
| return new Promise(resolve => setTimeout(resolve, ms)); | ||
| } | ||
|
|
||
| try { | ||
| const { setModuleImports, getAssemblyExports } = await dotnet | ||
| .withConfig({ appendElementOnExit: true, exitOnUnhandledError: true, forwardConsole: true, logExitCode: true }) | ||
| .withDiagnosticTracing(true) | ||
| .withApplicationArguments("--meaning", "42") | ||
| .create(); |
| using System; | ||
| using System.Runtime.InteropServices.JavaScript; | ||
| using System.Runtime.InteropServices; | ||
| using System.Threading.Tasks; |
| /// </summary> | ||
| [Output] | ||
| public ITaskItem[] WebcilSizes { get; set; } |
30537db to
2e01eda
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:994
- Same issue as
_WasmFeedReadyToRunCompileList: this CoreCLR-only ordering target should be gated onUseMonoRuntime == 'false'rather thanUseMonoRuntime != 'true'to avoid running whenUseMonoRuntimeis unset (which this file treats as Mono workload ownership).
Condition="'$(UseMonoRuntime)' != 'true' and '$(PublishReadyToRun)' == 'true' and '$(PublishReadyToRunComposite)' != 'true' and '$(WasmBuildingForNestedPublish)' != 'true'"
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:969
- The CoreCLR-only R2R targets are gated by
UseMonoRuntime != 'true', but this file documents that CoreCLR is indicated byUseMonoRuntime == 'false'and Mono can betrueor unset. With the current condition, an unsetUseMonoRuntimewould incorrectly enable this target in Mono scenarios.
This issue also appears on line 994 of the same file.
Condition="'$(UseMonoRuntime)' != 'true' and '$(PublishReadyToRun)' == 'true' and '$(PublishTrimmed)' == 'true' and '$(PublishReadyToRunComposite)' != 'true' and '$(IntermediateLinkDir)' != ''"
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj:122
- Using a wildcard include for the framework R2R outputs ("$(_WasmFrameworkR2RDir)*.wasm") can accidentally pick up stale .wasm files from previous builds (e.g., if WasmFrameworkR2RSubset changes or the set of inputs shrinks), causing unintended assemblies to be packaged. It’s safer to package exactly the outputs corresponding to the current computed input list.
<ItemGroup>
<_WasmFrameworkR2ROutput Include="$(_WasmFrameworkR2RDir)*.wasm" />
<LibrariesRuntimeFiles Include="@(_WasmFrameworkR2ROutput)" IsNative="true" />
<PlatformManifestFileEntry Include="@(_WasmFrameworkR2ROutput->'%(FileName)%(Extension)')" IsNative="true" />
</ItemGroup>
src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props:243
- This entry adds a CoreCLR R2R webcil (System.Private.CoreLib.wasm) under a section labeled "Mono WASM-specific files", which is now misleading and makes it harder to understand ownership of the manifest list. Consider updating the section comment to reflect that it includes WASM files shared across runtimes (Mono/CoreCLR).
<PlatformManifestFileEntry Include="System.Private.CoreLib.wasm" IsNative="true" />
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj:99
- The framework R2R crossgen command unconditionally passes
-r:"$(_WasmCoreLibIL)", but_WasmCoreLibILcan remain empty if neither candidate path exists. That would produce-r:""and likely fails in a confusing way. Please fail early with a clear error (or gate the target) when CoreLib IL isn't found.
<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>
src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj:87
_WasmFrameworkR2RInputglobs all shared-framework*.dllfiles. IfSystem.Private.CoreLib.dllever appears under$(LibrariesSharedFrameworkBinArtifactsPath)(there’s already fallback logic elsewhere for CoreLib living outside native/), this target would also crossgen CoreLib and emit a secondSystem.Private.CoreLib.wasm, risking duplicate/ambiguous pack contents. It seems safer to exclude CoreLib explicitly here and keep its R2R production in the existing dedicated pipeline.
This issue also appears on line 94 of the same file.
<_WasmFrameworkR2RInput Include="$(LibrariesSharedFrameworkBinArtifactsPath)*.dll"
Exclude="$(LibrariesSharedFrameworkBinArtifactsPath)*.resources.dll" />
<!-- Optional subset filter for bring-up: keep only assemblies named in WasmFrameworkR2RSubset. -->
c8ade58 to
65b32e6
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/mono/browser/build/WasmApp.InTree.props:38
WasmApp.ReadyToRun.targetsmirrorsAppleBuild.ReadyToRun.targetsby usingDependsOnTargets="$(ResolveReadyToRunCompilersDependsOn)", butWasmApp.InTree.propsnever setsResolveReadyToRunCompilersDependsOn. In in-tree builds this can preventResolveRuntimeFilesFromLocalBuild(and its artifact validation/paths) from running before resolving the Crossgen2 tool, unlike the Apple path which sets this property.
<PropertyGroup Condition="'$(RuntimeFlavor)' == 'CoreCLR' and '$(TargetOS)' == 'browser' and '$(PublishReadyToRun)' == 'true'">
<PublishReadyToRunContainerFormat Condition="'$(PublishReadyToRunContainerFormat)' == ''">wasm</PublishReadyToRunContainerFormat>
<!-- Per-assembly (non-composite) for bring-up; composite is a later phase. -->
<PublishReadyToRunComposite Condition="'$(PublishReadyToRunComposite)' == ''">false</PublishReadyToRunComposite>
<!-- crossgen2 wasm codegen cannot emit some NYI/SIMD sequences into R2R; fall back to runtime JIT for those. -->
<PublishReadyToRunCrossgen2ExtraArgs>$(PublishReadyToRunCrossgen2ExtraArgs);--codegenopt:JitWasmNyiToR2RUnsupported=1;--codegenopt:JitWasmSimdNyiToR2RUnsupported=1</PublishReadyToRunCrossgen2ExtraArgs>
<AfterMicrosoftNETSdkTargets Condition="'$(Crossgen2SdkOverrideTargetsPath)' != '' and Exists('$(Crossgen2SdkOverrideTargetsPath)')">$(AfterMicrosoftNETSdkTargets);$(Crossgen2SdkOverrideTargetsPath)</AfterMicrosoftNETSdkTargets>
<AfterMicrosoftNETSdkTargets>$(AfterMicrosoftNETSdkTargets);$(MSBuildThisFileDirectory)WasmApp.ReadyToRun.targets</AfterMicrosoftNETSdkTargets>
</PropertyGroup>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Runtime.CoreCLR.sfxproj:98
- _WasmCoreLibIL can remain empty if neither CoreCLRArtifactsPath location exists. In that case the later crossgen Exec will pass
-r:"", which fails with a hard-to-diagnose error. Add an explicit check so the build fails early with an actionable message when CoreLib IL can't be found.
<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>
src/mono/browser/build/BrowserWasmApp.CoreCLR.targets:633
- This remap only happens when $(IntermediateLinkDir) is set. If PublishReadyToRun is enabled for a non-trimmed publish (so ILLink doesn't run and IntermediateLinkDir is empty), WasmAssembliesToBundle may still contain the R2R webcil-in-wasm “.dll” outputs, which MetadataLoadContext can't read, and the generator will regress. Consider either (a) explicitly requiring PublishTrimmed here, or (b) providing an alternative IL path/source for the non-trimmed R2R case so the scan always uses IL assemblies.
<ItemGroup Condition="'$(PublishReadyToRun)' == 'true' and '$(IntermediateLinkDir)' != ''">
<_WasmManagedAssembliesR2RRemap Include="@(_WasmManagedAssemblies)" Condition="Exists('$(IntermediateLinkDir)%(FileName)%(Extension)')" />
<_WasmManagedAssemblies Remove="@(_WasmManagedAssembliesR2RRemap)" />
<_WasmManagedAssemblies Include="@(_WasmManagedAssembliesR2RRemap->'$(IntermediateLinkDir)%(FileName)%(Extension)')" />
</ItemGroup>
No description provided.