[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation - #131658

Merged
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host
Aug 7, 2026
Merged

[wasm] Emit webcil payloadSize/tableSize into boot config for streaming instantiation#131658
pavelsavara merged 4 commits into
dotnet:mainfrom
pavelsavara:browsehost_load_r2r_host

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Jul 31, 2026

Copy link
Copy Markdown
Member

Split from #129634

Summary

The WASM loader previously instantiated each webcil-in-wasm assembly by buffering its bytes and calling the module's getWebcilSize/getWebcilPayload exports to discover the payload size at runtime. This records the payload size — and, for ReadyToRun images, the indirect-call table size — in the boot config so the loader can stream-instantiate the module directly.

Changes

  • Boot config (BootJsonData): a new WebcilAsset : GeneralAsset type carries optional payloadSize (emitted for every webcil-in-wasm assembly) and tableSize (R2R images only). The assembly / coreAssembly / lazyAssembly / satellite asset lists use this type; both fields use EmitDefaultValue = false, so non-webcil assets are unchanged. Only emitted for net11.0+.
  • Size reader: WebcilReader.TryReadWebcilInWasmSizes reads payloadSize (+ tableSize for R2R) from data segment 0 of the produced webcil, reusing the existing WasmModuleReader. Covered by unit tests (WebcilInWasmSizesTests).
  • Build tasks: a dedicated AttachWebcilSizes task reads the sizes from the already-produced webcil files and attaches them as PayloadSize/TableSize metadata on the resource items. New _AttachBuildWebcilSizes / _AttachPublishWebcilSizes targets run on every boot-config generation (cheap: only the small size header is read), so the metadata is present even on incremental builds where the convert task was skipped for unchanged assemblies. GenerateWasmBootJson consumes the metadata directly and errors if it is missing — there is no file-reading fallback and ConvertDllsToWebcil no longer computes sizes.
  • Loader / host (host/assets.ts, loader/assets.ts): instantiateWebcilModule takes payloadSize/tableSize and uses WebAssembly.instantiateStreaming when possible, with no byte buffering and no data-section parsing. Instantiation is wrapped so the payload buffer is freed on failure. For R2R images it wires the host ABI handshake (stack pointer, exception tag, indirect-call table + base, image base) and grows the table before instantiation.

Notes

  • payloadSize is emitted for all webcil-in-wasm assemblies; tableSize only appears once ReadyToRun webcil-in-wasm images are produced (separate follow-up work — R2RWebcilPath is consumed here but produced by that work).
  • Public loader API surface adds an optional WebcilAsset type (payloadSize/tableSize) in public-api.ts.

Note

This pull request description was generated with the assistance of GitHub Copilot.

…ng instantiation
The WASM loader previously instantiated each webcil-in-wasm assembly by buffering
its bytes and then calling the module's getWebcilSize/getWebcilPayload exports to
discover the payload size at runtime. This records the payload size (and, for
ReadyToRun images, the indirect-call table size) in the boot config so the loader
can stream-instantiate directly.
- Boot config: GeneralAsset gains optional payloadSize (emitted for every
webcil-in-wasm assembly) and tableSize (R2R images only). Both use
EmitDefaultValue=false so non-R2R assets stay unchanged apart from payloadSize.
- ConvertDllsToWebcil computes the sizes from the produced webcil's wasm data
segment and exposes them via a new WebcilSizes output. GenerateWasmBootJson
consumes that output, with a self-contained fallback that re-reads the sizes
straight from the produced webcil when the incremental convert task was skipped
(e.g. a boot-config property changed but no assembly did).
- Loader/host: instantiateWebcilModule now takes payloadSize/tableSize and uses
WebAssembly.instantiateStreaming when possible, without buffering the bytes or
parsing the wasm data section. For R2R images it wires the host ABI handshake
(stack pointer, exception tag, indirect-call table + base, image base) and grows
the table before instantiation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara
pavelsavara requested a review from marafJuly 31, 2026 17:23
@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Host and removed area-Build-mono labels Jul 31, 2026
@pavelsavarapavelsavara added this to the 11.0.0 milestone Jul 31, 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.

CopilotAI 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.

Pull request overview

This PR threads Webcil-in-wasm payload sizing (and future R2R table sizing) from the build pipeline into the generated boot config, then updates the JS loader/host to use those sizes to instantiate WebAssembly modules without buffering and data-section parsing.

Changes:

  • Extend boot config assets to optionally include payloadSize (all webcil-in-wasm) and tableSize (R2R-only), and plumb these values through boot config generation.
  • Teach the ConvertDllsToWebcil task to compute sizes by parsing the produced wasm and emit them as an MSBuild output; add a GenerateWasmBootJson fallback that re-reads sizes from disk when the convert task is skipped incrementally.
  • Update the JS host/loader to pass sizes into instantiateWebcilModule and prefer streaming instantiation when the response MIME type allows it.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.csCollects Webcil sizes (task output or fallback re-read) and passes them into asset emission.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.csComputes payloadSize/tableSize by parsing produced wasm and exposes them via a new MSBuild output.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.csAdds optional payloadSize / tableSize fields to the emitted asset shape.
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.csMaps computed sizes onto emitted GeneralAsset entries (including culture-qualified satellites).
src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.jsPreserves additional wasm exports needed for the R2R host ABI handshake.
src/native/libs/Common/JavaScript/types/public-api.tsAdds a public WebcilAsset type documenting payloadSize/tableSize.
src/native/libs/Common/JavaScript/types/internal.tsExtends internal asset entries to carry payloadSize/tableSize through loader pipelines.
src/native/libs/Common/JavaScript/types/ems-ambient.tsExtends ambient emscripten typings for additional preserved wasm exports.
src/native/libs/Common/JavaScript/loader/assets.tsPasses tableSize/payloadSize from assets into the host instantiation call.
src/native/libs/Common/JavaScript/host/assets.tsImplements size-aware (streaming when possible) Webcil module instantiation and R2R import wiring.
src/mono/sample/wasm/Directory.Build.targetsIncludes PublishReadyToRun in nested sample build propagation.
src/mono/sample/wasm/Directory.Build.propsFlows nested PublishReadyToRun into the outer build property when unset.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targetsWires the new WebcilSizes task output into boot config generation for build and publish.

Comment threadsrc/native/libs/Common/JavaScript/host/assets.ts Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/mono/sample/wasm/Directory.Build.props
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
Comment threadsrc/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs Outdated
CopilotAI review requested due to automatic review settings August 5, 2026 11:39

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:214

  • RecordWebcilSize logs an error when TryReadWebcilSizes fails, but it currently records (and later emits) payloadSize/tableSize even when the parsed payloadSize is 0/negative or tableSize is negative (e.g., due to a malformed/corrupt .wasm or uint32->int overflow). Since payloadSize is required for every webcil-in-wasm assembly, validate the parsed sizes here and fail fast with an actionable error before emitting WebcilSizes metadata.
 if (!TryReadWebcilSizes(webcilPath, out int payloadSize, out int tableSize, out string failureReason))
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{webcilPath}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return;
}

src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets:1005

  • _WasmWebcilSizes is populated both during build (_ConvertBuildDllsToWebcil) and again during publish. Because WebcilSizes ItemSpec keys are only culture/filename, a publish invocation that skips ConvertDllsToWebcil can accidentally reuse build-time sizes (preventing GenerateWasmBootJson’s fallback from re-reading the actual publish output). Clear the item group (or split build vs publish item names) before the publish ConvertDllsToWebcil call to avoid cross-phase collisions.
 <ConvertDllsToWebcil Candidates="@(_NewWasmPublishStaticWebAssets)" IntermediateOutputPath="$(_WasmPublishTmpWebcilPath)" OutputPath="$(_WasmPublishWebcilPath)" IsEnabled="$(_WasmEnableWebcil)" WebcilVersion="$(_WasmWebcilVersion)">
<Output TaskParameter="WebcilCandidates" ItemName="_NewWebcilPublishStaticWebAssetsCandidates" />
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
<Output TaskParameter="WebcilSizes" ItemName="_WasmWebcilSizes" />
</ConvertDllsToWebcil>

src/native/libs/Common/JavaScript/host/assets.ts:73

  • instantiateWebcilModule now duplicates instantiateWasm’s streaming/ArrayBuffer fallback logic, but it no longer uses instantiateWasm’s error handling (notably the catch that calls dotnetApi.exit(1, err)). That changes failure behavior for Webcil module instantiation and also duplicates the Content-Type check logic in a slightly different way. Reuse instantiateWasm here so Webcil instantiation follows the same streaming heuristics and fatal-error handling as other module loads.
 let instance: WebAssembly.Instance;
const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined;
const streamingOk = hasInstantiateStreaming && typeof globalThis.Response === "function" && res instanceof globalThis.Response && contentType === "application/wasm";
if (streamingOk) {
const instantiated = await WebAssembly.instantiateStreaming(res, imports);

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:444

  • GenerateWasmBootJson now has new behavior that must remain correct under incremental rebuilds: when ConvertDllsToWebcil is skipped, it falls back to reading payloadSize/tableSize directly from the produced webcil .wasm files to avoid emitting a boot config that the loader can’t consume. There are existing Wasm.Build.Tests that validate incremental boot-json regeneration; extending them to assert that payloadSize (and, when available, tableSize) is still present after a property-only rebuild would help prevent regressions in this critical incremental path.
 // Fast path: ConvertDllsToWebcil already computed the sizes. Fall back to
// reading them straight from the produced webcil when it didn't: that task is
// incremental and can be skipped while this boot config is regenerated (e.g. a
// boot-config property changed but no assembly did), which would otherwise drop
// payloadSize/tableSize and break the loader. R2R images especially need
// tableSize before instantiation.

CopilotAI review requested due to automatic review settings August 5, 2026 14:45

CopilotAI 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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:537

  • PayloadSize/TableSize are read as u32 but cast directly to int. If the high bit is set this will wrap to a negative value and TryReadWebcilInWasmSizes will return true, leaving callers to fail later (often with an empty failureReason). It should validate the u32 values are in range (and non-zero for payloadSize) and return false with a FailureReason when they are not.
 PayloadSize = (int)ReadUInt32LE(sizes, 0);
TableSize = want == 8 ? (int)ReadUInt32LE(sizes, 4) : 0;
HasSizes = true;

src/native/libs/Common/JavaScript/host/assets.ts:56

  • payloadSize/tableSize come from boot config; currently only payloadSize===0 is rejected and a negative/non-integer value would flow into posix_memalign/table.grow, potentially throwing or allocating incorrectly. This can also be triggered by u32→int overflow on the producer side. Validate both values are finite integers and enforce payloadSize>0 and tableSize>=0.
 if (typeof payloadSize !== "number" || payloadSize === 0) {
throw new Error(`Webcil asset '${virtualPath}' is missing payloadSize in the boot config.`);
}
const tableEntries = typeof tableSize === "number" ? tableSize : 0;

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ConvertDllsToWebCil.cs:149

  • Log message has an extra space before the period after the source path, which makes logs look sloppy and harder to search/compare.
 Log.LogMessage(MessageImportance.Low, $"Staged prebuilt R2R webcil {finalWebcil} from {r2rWebcilPath} .");

src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AttachWebcilSizes.cs:48

  • If TryReadWebcilInWasmSizes returns true but payloadSize is <= 0 (or if failureReason comes back null), the logged error message will include an empty reason: "(... )". Capture the ok flag separately and provide a fallback reason so failures are actionable.
 if (!WebcilReader.TryReadWebcilInWasmSizes(path, out int payloadSize, out int tableSize, out string failureReason) || payloadSize <= 0)
{
Log.LogError($"Could not read the Webcil payload/table sizes from '{path}' ({failureReason}). The runtime loader requires payloadSize for every webcil-in-wasm assembly.");
return false;
}

@pavelsavara
pavelsavara requested a review from marafAugust 5, 2026 15:04

@marafmaraf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 👍

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

@pavelsavara
pavelsavara merged commit 30eea45 into dotnet:mainAug 7, 2026
134 of 136 checks passed
@pavelsavara
pavelsavara deleted the browsehost_load_r2r_host branch August 7, 2026 15:46
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 11.0.0, 11.0-rc1Aug 7, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Host

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pavelsavara@maraf