[browser][coreCLR] browserhost to load R2R#129634
Draft
pavelsavara wants to merge 24 commits into
Draft
Conversation
Contributor
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
Contributor
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. |
This was referenced Jun 20, 2026
Comment on lines
+258
to
+265
| // 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); |
Comment on lines
+49
to
+55
| 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.`); | ||
| } |
Comment on lines
+424
to
+433
| <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" /> |
Comment on lines
+89
to
+94
| /// <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; } |
Comment on lines
+40
to
+47
| /// <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> |
Comment on lines
339
to
342
| 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; |
Comment on lines
+326
to
+336
| 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; |
Comment on lines
+6
to
+20
| 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(); |
3 tasks
…thods CallDescrWorkerInternal (the wasm reflection/CallDescr invoke path) resolved the interpreter byte code via MethodDesc::GetInterpreterCode() and, after a DoPrestub retry, passed the pointer straight to ExecuteInterpretedMethodWithArgs. When the target method has no interpreter byte code because it was compiled to native (R2R) code, GetInterpreterCode() returns NULL. On wasm a NULL pointer reads zeroed low linear memory instead of faulting, so the interpreter dispatched the NULL byte code as INTOP_INVALID and aborted with Unimplemented or invalid interpreter opcode plus a secondary stackwalk assert. Add the InvokeManagedMethod fallback (the interpreter->R2R thunk) when there is no interpreter code, mirroring the existing pattern in ExecuteInterpretedMethodWithArgs_PortableEntryPoint_Complex and the CALL_INTERP_METHOD path in InterpExecMethod. Also harden the sibling entry points: ExecuteInterpretedMethodFromUnmanaged now takes the same fallback, and ExecuteInterpretedMethodWithArgs asserts a non-NULL targetIp so future misuse fails loudly at the point of error.
Address PR dotnet#129766 review feedback: resolve R2R-compiled native code for UnmanagedCallersOnly methods directly instead of going through the interpreter reverse-thunk fallback. - GetUnmanagedCallersOnlyThunk (wasm/helpers.cpp): run DoPrestub under GCX_PREEMP to publish code, then return the native (R2R) entrypoint via PortableEntryPoint when present; the g_ReverseThunks fallback is only for interpreted methods. - ExecuteInterpretedMethodFromUnmanaged (prestub.cpp): R2R methods are now dispatched directly and never reach this path, so replace the InvokeManagedMethod fallback with an assert that the interpreter byte code exists. - Widen contracts for the prestub-on-resolve path: EnsureCodeForUnmanagedCallersOnly (precode_portable.cpp) is THROWS/GC_TRIGGERS/MODE_PREEMPTIVE; GetSingleCallableAddrOfCodeForUnmanagedCallersOnly (method.cpp) is GC_TRIGGERS under FEATURE_PORTABLE_ENTRYPOINTS.
8-byte locals were only 4-byte aligned on wasm, so JS interop reads through the 8-byte HEAP64/HEAPF64 views landed on the wrong word. Align >=8 byte locals to 8 and round the frame to STACK_ALIGN. Fixes dotnet#129802. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… frames 16-aligned)
This was referenced Jun 24, 2026
Open
…runtime into browsehost_load_r2r
Comment on lines
+144
to
+150
| if (!int.TryParse(s.GetMetadata("PayloadSize"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int ps) || ps <= 0) | ||
| { | ||
| Log.LogError($"Webcil asset '{s.ItemSpec}' has missing or invalid PayloadSize metadata; the runtime loader requires it."); | ||
| continue; | ||
| } | ||
| int.TryParse(s.GetMetadata("TableSize"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int ts); | ||
| webcilSizeByName[s.ItemSpec] = (ts, ps); |
Comment on lines
+23
to
+25
| <!-- ReadyToRun (R2R) for WebAssembly: tool/reference paths consumed by the SDK's | ||
| _WasmCrossgenReadyToRunAssemblies target when a project opts in via @(WasmReadyToRunAssembly). | ||
| These point at the in-tree build outputs; a shipping SDK would resolve crossgen2 from its pack. --> |
Comment on lines
+20
to
+22
| <!-- Opt the app assembly into ReadyToRun (R2R) cross-compilation. The WebAssembly SDK crossgens | ||
| each listed assembly to an R2R webcil-in-wasm image before webcil conversion. See the | ||
| _WasmCrossgenReadyToRunAssemblies target in Microsoft.NET.Sdk.WebAssembly.Browser.targets. --> |
Comment on lines
+400
to
+414
| /// <summary> | ||
| /// For ReadyToRun (R2R) webcil-in-wasm images: the number of table entries the module needs. | ||
| /// When present (non-null) the loader grows the table before instantiation. Only R2R images set | ||
| /// this; it is omitted for plain (non-R2R) webcil. | ||
| /// </summary> | ||
| [DataMember(EmitDefaultValue = false)] | ||
| public int? tableSize { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The size in bytes of the Webcil payload to allocate before instantiation. Emitted for every | ||
| /// webcil-in-wasm assembly (the loader requires it to avoid parsing the wasm data section), not | ||
| /// just R2R images. For R2R images it is paired with <see cref="tableSize"/>. | ||
| /// </summary> | ||
| [DataMember(EmitDefaultValue = false)] | ||
| public int? payloadSize { get; set; } |
This was referenced Jun 25, 2026
…runtime into browsehost_load_r2r
Comment on lines
149
to
152
| WebcilCandidates = webcilCandidates.ToArray(); | ||
| PassThroughCandidates = passThroughCandidates.ToArray(); | ||
| WebcilSizes = _webcilSizes.ToArray(); | ||
| return true; |
Comment on lines
+177
to
+183
| CONTRACTL | ||
| { | ||
| THROWS; | ||
| GC_TRIGGERS; | ||
| MODE_PREEMPTIVE; | ||
| } | ||
| CONTRACTL_END; |
Comment on lines
+988
to
+991
| <PropertyGroup> | ||
| <_WasmCrossgen2NuGetRoot Condition="'$(_WasmCrossgen2NuGetRoot)' == ''">$(NuGetPackageRoot)</_WasmCrossgen2NuGetRoot> | ||
| <_WasmCrossgen2NuGetRoot Condition="'$(_WasmCrossgen2NuGetRoot)' == ''">$(NuGetPackageFolders)</_WasmCrossgen2NuGetRoot> | ||
| </PropertyGroup> |
Comment on lines
+1076
to
+1085
| <Error Condition="'$(_WasmCrossgen2Path)' == '' or !Exists('$(_WasmCrossgen2Path)')" | ||
| Text="ReadyToRun for WebAssembly requires crossgen2, but it was not found at '$(_WasmCrossgen2Path)'. Set the %24(_WasmCrossgen2Path) property." /> | ||
| <Error Condition="'$(_WasmCrossgen2JitPath)' == '' or !Exists('$(_WasmCrossgen2JitPath)')" | ||
| Text="ReadyToRun for WebAssembly requires the wasm JIT, but it was not found at '$(_WasmCrossgen2JitPath)'. Set the %24(_WasmCrossgen2JitPath) property." /> | ||
|
|
||
| <PropertyGroup> | ||
| <_WasmR2RLinkDir>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'linked'))</_WasmR2RLinkDir> | ||
| <_WasmR2ROutDir>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'r2r', 'publish'))</_WasmR2ROutDir> | ||
| <_WasmR2RRspPath>$(_WasmR2ROutDir)crossgen2.rsp</_WasmR2RRspPath> | ||
| </PropertyGroup> |
This was referenced Jun 25, 2026
Open
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.
No description provided.