Skip to content

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

Open
anurag6569201 wants to merge 1 commit into
qa/agent-dotnet-runtime/pr-09-131658/basefrom
qa/agent-dotnet-runtime/pr-09-131658/head
Open

anurag6569201 wants to merge 1 commit into
qa/agent-dotnet-runtime/pr-09-131658/basefrom
qa/agent-dotnet-runtime/pr-09-131658/head

Conversation

@anurag6569201

Copy link
Copy Markdown

Split from dotnet#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.

Source merge-base: c8f97975ab0d8ff84aebf176829e17f7c7052f03
Source head: 8a97f41ac2be07799ae9199d3405d667f3197b64

@shipwright-agent

Copy link
Copy Markdown

⛔ Shipwright · Blocked

Recommendation: do not merge PR #9 · Tier T2
Checks: 0 total · 0 needing attention

Next step: resolve the blocking findings before merge.

Findings (20)

  • CRITICAL The new test file references WebcilReader.TryReadWebcilInWasmSizes, but the diff does not add or modify that method in the WebcilReader.cs shown. · src/mono/wasm/Wasm.Build.Tests/WebcilInWasmSizesTests.cs:20
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • CRITICAL WebcilSizesModuleReader.VisitSection sets shouldStop = true for the Data section, but if the base WasmModuleReader.Visit() does not honor shouldStop or the module contains multiple · src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:480
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • CRITICAL GenerateWasmBootJson parses TableSize with int.TryParse but ignores the result. · src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:420
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The new test file WebcilInWasmSizesTests.cs references WebcilReader.TryReadWebcilInWasmSizes, but no implementation of this method is added or modified in the diff. · src/mono/wasm/Wasm.Build.Tests/WebcilInWasmSizesTests.cs:20
    • Fix: Fix the review finding before release.
  • HIGH The WebcilSizesModuleReader.VisitSection method sets shouldStop = true for the Data section, but the base WasmModuleReader.Visit() may not stop after the first Data section. · src/tasks/Microsoft.NET.WebAssembly.Webcil/WebcilReader.cs:480
    • Fix: Fix the review finding before release.
  • HIGH The code uses int.TryParse for TableSize without checking the result. · src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:420
    • Fix: Fix the review finding before release.
  • HIGH The webcilSizes dictionary key format is documented as matching BootJsonBuilderHelper, but the helper's lookup uses subFolder + '/' + a.Key while GenerateWasmBootJson builds webcil · src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:245
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The isWebcilInWasmAssembly check treats every .wasm asset that is not the native runtime wasm as webcil-in-wasm and requires PayloadSize metadata. · src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs:410
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • …and 12 more findings in the check details.

Fireworks usage: 37,370 input · 2,802 output · 40,172 total tokens · $0.0101 · 29s · 0 fix iteration(s)

Open the Shipwright check for full evidence and the audit bundle. Use /shipwright rerun to verify again.

public void NonR2R_ReadsPayloadSize_WithZeroTableSize()
{
byte[] wasm = BuildWebcilInWasm(payloadSize: 0x1234, tableSize: null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · CRITICAL

The new test file references WebcilReader.TryReadWebcilInWasmSizes, but the diff does not add or modify that method in the WebcilReader.cs shown.

Impact: The new test file references WebcilReader.TryReadWebcilInWasmSizes, but the diff does not add or modify that method in the WebcilReader.cs shown. If the method does not already exist in the base branch, the test project will fail to compile, breaking CI. The diff only adds WebcilSizesModuleReader and the public TryReadWebcilInWasmSizes overloads are present in the shown WebcilReader.cs hunk, but the review evidence…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

int r = stream.Read(buffer, read, count - read);
if (r == 0)
return false;
read += r;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · CRITICAL

WebcilSizesModuleReader.VisitSection sets shouldStop = true for the Data section, but if the base WasmModuleReader.Visit() does not honor shouldStop or the module contains multiple

Impact: WebcilSizesModuleReader.VisitSection sets shouldStop = true for the Data section, but if the base WasmModuleReader.Visit() does not honor shouldStop or the module contains multiple Data sections, later sections can overwrite HasSizes/PayloadSize/TableSize or fail after the first Data section. This can produce incorrect payload/table sizes in the boot config, causing runtime allocation/table-growth failures for valid…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

if (isWebcilInWasmAssembly)
{
if (!int.TryParse(resource.GetMetadata("PayloadSize"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int ps) || ps <= 0)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · CRITICAL

GenerateWasmBootJson parses TableSize with int.TryParse but ignores the result.

Impact: GenerateWasmBootJson parses TableSize with int.TryParse but ignores the result. If TableSize metadata is missing or malformed, ts defaults to 0 and the R2R image is recorded as non-R2R. The loader then skips table growth and R2R imports, leading to instantiation failure or incorrect indirect-call behavior at runtime.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

public void NonR2R_ReadsPayloadSize_WithZeroTableSize()
{
byte[] wasm = BuildWebcilInWasm(payloadSize: 0x1234, tableSize: null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The new test file WebcilInWasmSizesTests.cs references WebcilReader.TryReadWebcilInWasmSizes, but no implementation of this method is added or modified in the diff.

Impact: The new test file WebcilInWasmSizesTests.cs references WebcilReader.TryReadWebcilInWasmSizes, but no implementation of this method is added or modified in the diff. The tests will not compile unless the method already exists elsewhere, and the PR title indicates the reader is part of this change. This is a concrete build/test failure introduced by the diff.

Suggested fix: Fix the review finding before release.

int r = stream.Read(buffer, read, count - read);
if (r == 0)
return false;
read += r;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The WebcilSizesModuleReader.VisitSection method sets shouldStop = true for the Data section, but the base WasmModuleReader.Visit() may not stop after the first Data section.

Impact: The WebcilSizesModuleReader.VisitSection method sets shouldStop = true for the Data section, but the base WasmModuleReader.Visit() may not stop after the first Data section. If the module contains multiple Data sections (allowed by the wasm spec), the reader will continue visiting subsequent sections and may overwrite HasSizes/PayloadSize/TableSize or fail on a later Data section, producing incorrect sizes. The code…

Suggested fix: Fix the review finding before release.

if (isWebcilInWasmAssembly)
{
if (!int.TryParse(resource.GetMetadata("PayloadSize"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int ps) || ps <= 0)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The code uses int.TryParse for TableSize without checking the result.

Impact: The code uses int.TryParse for TableSize without checking the result. If TableSize metadata is missing or invalid, ts defaults to 0, which is then stored in webcilSizes. This may cause the runtime to treat an R2R image as non-R2R, leading to incorrect table growth and potential runtime failure.

Suggested fix: Fix the review finding before release.

// cultures, so qualify by culture to avoid collisions. It matches how
// BootJsonBuilderHelper resolves webcilSizes per (culture subfolder, route).
string webcilCulture = string.Equals("Culture", assetTraitName, StringComparison.OrdinalIgnoreCase) ? assetTraitValue : null;
string r2rSizeStoreKey = webcilCulture != null ? webcilCulture + "/" + resourceRoute : resourceRoute;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The webcilSizes dictionary key format is documented as matching BootJsonBuilderHelper, but the helper's lookup uses subFolder + '/' + a.Key while GenerateWasmBootJson builds webcil

Impact: The webcilSizes dictionary key format is documented as matching BootJsonBuilderHelper, but the helper's lookup uses subFolder + '/' + a.Key while GenerateWasmBootJson builds webcilCulture + '/' + resourceRoute. The diff does not demonstrate that resourceRoute and a.Key are always identical, so a mismatch would silently omit payloadSize/tableSize from boot JSON and break the loader at runtime.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

// so the runtime loader can instantiate without parsing the wasm. payloadSize is
// emitted for every webcil; tableSize only for R2R. Identify them by the produced
// ".wasm" extension, excluding native wasm (dotnet.native.wasm) which is handled
// separately and is not a webcil module. The AttachWebcilSizes task has already

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The isWebcilInWasmAssembly check treats every .wasm asset that is not the native runtime wasm as webcil-in-wasm and requires PayloadSize metadata.

Impact: The isWebcilInWasmAssembly check treats every .wasm asset that is not the native runtime wasm as webcil-in-wasm and requires PayloadSize metadata. If any non-webcil .wasm asset reaches this path, the build logs an error and skips the resource, potentially breaking valid builds. The check should positively identify webcil-in-wasm assets rather than exclude only dotnet.native.wasm.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

}

protected override bool VisitSection(Section sec, out bool shouldStop)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The data segment 0 reader accepts any segment length >= 4 and reads 8 bytes when length >= 8, without validating that the segment contains exactly the expected 4-byte payload size

Impact: The data segment 0 reader accepts any segment length >= 4 and reads 8 bytes when length >= 8, without validating that the segment contains exactly the expected 4-byte payload size or 8-byte payload+table size header. A malformed or unexpected segment layout can produce incorrect sizes that are then emitted into boot JSON.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

failureReason = ex.Message;
return false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

ReadUInt32LE shifts byte values without first casting to uint.

Impact: ReadUInt32LE shifts byte values without first casting to uint. In C#, byte promotes to int, so a byte with the high bit set can sign-extend during the shift and produce an incorrect uint value for payloadSize or tableSize.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

failureReason = null;
try
{
using var reader = new WebcilSizesModuleReader(stream);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

TryReadWebcilInWasmSizes catches all exceptions and converts them into a false result with the exception message.

Impact: TryReadWebcilInWasmSizes catches all exceptions and converts them into a false result with the exception message. This can mask IO failures, OutOfMemoryException, or other critical errors as 'not a webcil-in-wasm image', allowing the build to proceed without required size metadata and deferring failure to runtime. The catch-all also risks leaking filesystem paths or internal exception details into build logs.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

}

protected override bool VisitSection(Section sec, out bool shouldStop)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · MEDIUM

The TryReadWebcilInWasmSizes method reads the payload size from data segment 0, but the comment says 'The webcil payload lives in a later data segment, so only the small size heade

Impact: The TryReadWebcilInWasmSizes method reads the payload size from data segment 0, but the comment says 'The webcil payload lives in a later data segment, so only the small size header is read.' If the payload size is stored in data segment 0, the code reads only 4 or 8 bytes from the segment, but the segment may contain additional data. The code does not validate that the segment length is exactly 4 or 8 bytes, so it…

Suggested fix: Fix the review finding before release.

// cultures, so qualify by culture to avoid collisions. It matches how
// BootJsonBuilderHelper resolves webcilSizes per (culture subfolder, route).
string webcilCulture = string.Equals("Culture", assetTraitName, StringComparison.OrdinalIgnoreCase) ? assetTraitValue : null;
string r2rSizeStoreKey = webcilCulture != null ? webcilCulture + "/" + resourceRoute : resourceRoute;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · MEDIUM

The webcilSizes dictionary is keyed by r2rSizeStoreKey, which is either resourceRoute or webcilCulture + '/' + resourceRoute.

Impact: The webcilSizes dictionary is keyed by r2rSizeStoreKey, which is either resourceRoute or webcilCulture + '/' + resourceRoute. However, the comment says 'It matches how BootJsonBuilderHelper resolves webcilSizes per (culture subfolder, route).' If the BootJsonBuilderHelper uses a different key format (e.g., culture subfolder without a slash, or a different separator), the lookup will fail, and the sizes will not be e…

Suggested fix: Fix the review finding before release.

// so the runtime loader can instantiate without parsing the wasm. payloadSize is
// emitted for every webcil; tableSize only for R2R. Identify them by the produced
// ".wasm" extension, excluding native wasm (dotnet.native.wasm) which is handled
// separately and is not a webcil module. The AttachWebcilSizes task has already

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · MEDIUM

The isWebcilInWasmAssembly check uses IsTargeting110OrLater() and fileExtension == '.wasm', but it does not verify that the asset is actually a webcil-in-wasm assembly.

Impact: The isWebcilInWasmAssembly check uses IsTargeting110OrLater() and fileExtension == '.wasm', but it does not verify that the asset is actually a webcil-in-wasm assembly. It only excludes native wasm. If there are other .wasm assets that are not webcil-in-wasm, the code will attempt to read PayloadSize metadata and log an error if missing, potentially failing the build for valid assets.

Suggested fix: Fix the review finding before release.


private sealed class WebcilSizesModuleReader : WasmModuleReader
{
internal bool HasSizes { get; private set; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · MEDIUM

The WebcilSizesModuleReader.VisitSection method reads the data segment 0 using TryReadPassiveDataSegment, but it does not check if the segment is actually passive.

Impact: The WebcilSizesModuleReader.VisitSection method reads the data segment 0 using TryReadPassiveDataSegment, but it does not check if the segment is actually passive. The method name suggests it checks for passive segments, but the code does not verify the segment's mode. If the segment is active, the start offset may be incorrect, leading to reading wrong data.

Suggested fix: Fix the review finding before release.

failureReason = ex.Message;
return false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · MEDIUM

The ReadUInt32LE method uses bitwise OR and shifts on bytes, but the bytes are signed in C#.

Impact: The ReadUInt32LE method uses bitwise OR and shifts on bytes, but the bytes are signed in C#. If the high bit of a byte is set, the shift operation may produce incorrect results due to sign extension. The code should cast to uint before shifting.

Suggested fix: Fix the review finding before release.

byte[] notWasm = { 0x7f, 0x45, 0x4c, 0x46, 0x00, 0x00, 0x00, 0x00 };

using var stream = new MemoryStream(notWasm);
bool ok = WebcilReader.TryReadWebcilInWasmSizes(stream, out _, out _, out string? failureReason);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · LOW

The test comment says the segment is 'smaller than the 4-byte payload size' but the body writes only 2 bytes.

Impact: The test comment says the segment is 'smaller than the 4-byte payload size' but the body writes only 2 bytes. The test behavior is correct, but the comment is misleading about the exact size used.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

byte[] notWasm = { 0x7f, 0x45, 0x4c, 0x46, 0x00, 0x00, 0x00, 0x00 };

using var stream = new MemoryStream(notWasm);
bool ok = WebcilReader.TryReadWebcilInWasmSizes(stream, out _, out _, out string? failureReason);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · LOW

The test DataSegmentTooSmall_Fails constructs a data section with a passive segment of length 2, but the reader is expected to read a 4-byte payload size.

Impact: The test DataSegmentTooSmall_Fails constructs a data section with a passive segment of length 2, but the reader is expected to read a 4-byte payload size. The test asserts failure, which is correct, but the comment says 'smaller than the 4-byte payload size' while the body writes only 2 bytes. This is a documentation mismatch in the test comment, not a functional failure.

Suggested fix: Fix the review finding before release.

public string cache { get; set; }
}

[DataContract]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · LOW

The new WebcilAsset class inherits from GeneralAsset and adds nullable int?

Impact: The new WebcilAsset class inherits from GeneralAsset and adds nullable int? tableSize and payloadSize. However, the internal.ts AssetEntryInternal adds tableSize?: number and payloadSize?: number. The C# DataMember EmitDefaultValue=false will omit null values, but the TypeScript interface does not mark them as nullable, which is consistent with optional properties. No concrete defect is demonstrated.

Suggested fix: Fix the review finding before release.

failureReason = null;
try
{
using var reader = new WebcilSizesModuleReader(stream);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · LOW

The TryReadWebcilInWasmSizes method catches all exceptions and returns false with the exception message.

Impact: The TryReadWebcilInWasmSizes method catches all exceptions and returns false with the exception message. This may hide critical errors (e.g., OutOfMemoryException, IOException) and treat them as 'not a webcil-in-wasm image', causing the build to proceed without the required sizes and potentially fail at runtime.

Suggested fix: Fix the review finding before release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant