[wasm] Make CoreCLR WebCIL images self-installing (wrapper version 2) - #134312
Conversation
A wasm R2R image previously emitted its webcil payload and R2R function table as passive segments and imported seven things under short names, so the WASI splice pipeline had to rewrite the segments and rename the imports after linking. Emit both correctly in the first place. An image that carries code - a composite or a single-assembly R2R image - now emits the payload as an active data segment at (global.get __memory_base) and the function table as an active element segment at (global.get __table_base), so the engine installs both at instantiation. Such an image exports patchWebcilHeader in place of getWebcilPayload/fillWebcilTable: memory.init and table.init against an active segment trap, because an active segment is implicitly dropped once applied, and the only remaining work is the header's tableBase field, which the runtime reads from the mapped image. Per-assembly component forwarding stubs keep the passive shape. A stub may be parsed from its file rather than instantiated, and the WASI host locates its payload by passive data segment index. Import names now match what wasm-ld exports, collapsing the R2R/NativeAOT split in WasmWellKnownGlobalSymbolNode rather than adding a second one, so a merged image resolves by name with no renaming step. The webcilCount segment stays passive in both shapes: the host loader reads it out of the module bytes before instantiation to size its allocation. Both JS loaders feature-detect the two shapes rather than assuming one. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
TryFindWebcilInWasm only inspected passive data segments, so it could not locate the payload of a self-installing image and reported "Unknown file format". That broke r2rdump and the WasmWebcilModule test, both of which read single-assembly R2R wasm images through this reader. Inspect every data segment for the Webcil magic regardless of kind, skipping an active segment's offset expression first. ParseElemSection already handled both forms, so only the data side needed it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
__memory_base and __table_base belong to the emscripten/wasm-ld dynamic linking ABI, where they carry a per-side-module meaning. Reusing them for the R2R image and table bases is correct only while the host is a non-PIC main module; -sMAIN_MODULE would give the linker its own definitions and collide. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A self-installing image leaves __memory_base and __table_base as imports, and the two ways a host can satisfy them are not equivalent. Supplying them at instantiation keeps the segment offsets a global.get of an imported global, which is a valid constant expression and needs no further processing. Defining and exporting them, then merging the image into the host, internalizes the globals; global.get of a defined global is only a constant expression under the GC proposal, engines disagree, and the merged module has to have its offsets folded to i32.const to be portable. Record that the fold is not free, since the pass that performs it also propagates globals into function bodies, and that a host reserving the table slice at link time can treat __table_base as the constant 1 while __memory_base must be read out of the linked host. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The remark named WasiExtractStubPayload, which lives in an offline host that is not in this tree, so a reader cannot follow the reference. State the constraint itself instead: a stub may be parsed as a file rather than instantiated, and an active segment would defeat locating its payload by passive data segment index. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @dotnet/crossgen-contrib |
|
I extracted this from #133265 because it changes the webcil format and is reviewable separately |
|
An alternative is dropping wecil for wasi r2r now. The main change here is using well known symbol names that follow the dynamic linking design and work with tooling as a convenient way for the R2R image to be position-independent until the host/layout is known. After composition, the ideal end state is effectively static. |
|
cc @dotnet/wasm-contrib |
The self-installing WebCIL R2R image shape emits the payload as an active data segment based at __memory_base. Update the existing alignment test to parse and assert that active segment header instead of expecting the old passive segment shape. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a519827b-7ffd-4499-92f6-8f94d61ea940
…asm-self-installing-segments
…asm-self-installing-segments
DwarfHelper.ReadULEB128 returns ulong, so keep the parsed active payload global index as ulong before comparing it with the WebCIL image base global index. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a519827b-7ffd-4499-92f6-8f94d61ea940
…asm-self-installing-segments
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
Thank you for explanation, that changes a lot. It means that we need to keep |
Keep boot-config allocation sizes and streaming instantiation for IL-only and R2R images. Remove duplicated runtime layout validation and verify active images and passive component stubs through the existing compiler tests. Document the matching-artifact contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a519827b-7ffd-4499-92f6-8f94d61ea940
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Critical host compatibility aliases and active data-segment mode handling remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 7
Open (7)
Missing legacy WebCIL import aliases breaks old R2R images · New Renamed WebCIL imports make passive fallback unreachable · New Flag 2 data segments are misparsed, bypassing stale-image checks · New Reject active payloads without expected base global Validate active segment offset uses __memory_base Validate active segment size before host instantiation Preserve legacy R2R import aliases or add ABI dispatch
Consume the explicit memory index for data-segment mode 2 before reading the offset expression. Cover metadata/MVID extraction and stale-image rejection with zero and multi-byte memory indices. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a519827b-7ffd-4499-92f6-8f94d61ea940
This PR is adding more complexity to CoreCLR host, but it should be removing it. I'm ok to leave the Mono on copy of old ConvertDllsToWebcil. |
…asm-self-installing-segments
Thanks, that settles it. Moving forward in this PR with:
One follow-up for #133265: its WASI composer uses passive vs. active to tell component stubs from composites, so it will need to classify them another way. Note This comment was drafted with GitHub Copilot. |
Emit every CoreCLR WebCIL image - IL-only, R2R, composite, and composite component stubs - with an active payload segment at __memory_base and, where there is a table, an active element segment at __table_base. Remove getWebcilPayload and fillWebcilTable from the R2R writer, the CoreCLR IL-only wrapper, corerun, and the browser host; hosts now require webcilVersion 2 and call patchWebcilHeader only for R2R images. Allocation from payloadSize/tableSize and streaming instantiation are unchanged. Mono keeps the passive version 0 wrapper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a519827b-7ffd-4499-92f6-8f94d61ea940
## Summary Enable composite ReadyToRun (R2R) publishing for CoreCLR on WASI. This builds on the self-installing WebCIL R2R images from #134312, which is now in `main`. Per review feedback, this PR covers publishing and composition only. The WASI R2R runtime-test and trimmed library-test CI lanes, their test harness plumbing, and the related library test quarantines are in #134813, which is stacked on this PR. ## Implementation - The WASI app builder compiles the app and framework closure into a composite R2R image plus per-assembly forwarding stubs using Crossgen2. The per-app WASI host (`wasihost`) reserves an image buffer and function-table slice sized to that composite, and exports the symbols the composite imports. - A C# `ComposeWasiReadyToRun` WasmAppBuilder task composes the self-installing composite into the linked host component. It reuses the WebCIL reader infrastructure, and uses `wasm-tools` and Binaryen for the post-link merge and global folding. Crossgen2's finished R2R Wasm is not a relocatable `wasm-ld` input, so this step cannot move into the native link. Before merging, the task checks the image buffer size and the table reservation against the host's exported values. - The `wasihost` external assembly probe serves the embedded composite and the per-assembly WebCIL stubs that are extracted at build time. - In-tree publishing acquires pinned `wasm-tools`/Binaryen versions into the shared wasm tool cache (`eng/AcquireWasiR2RTools.targets`). - Docs: `docs/workflow/building/coreclr/wasi-r2r.md` and a WASI host composition section in `docs/design/mono/webcil.md`. ## Validation - `./build.sh -s clr+libs+packs -os wasi -arch wasm -c Release`: 0 warnings, 0 errors. - Clean `PublishTrimmed` + `PublishReadyToRun=true` publish of `src/mono/sample/wasi/console` with an empty tool cache. The pinned tools were acquired, composition succeeded, and the app ran under wasmtime. `DOTNET_ReadyToRunLogFile` showed `Ready to Run initialized successfully` for all six loaded assemblies (CoreLib, the app, System.Runtime, System.Console, System.Threading, System.Runtime.InteropServices). - A non-R2R CoreCLR WASI publish of the same sample still links against the weak placeholder buffer and runs. Related: #130129. Follow-up: #134813. > [!NOTE] > This pull request description was prepared with assistance from GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 919fdde7-f547-4bfb-86ad-7ac02c626980


Summary
Make all CoreCLR WebAssembly WebCIL images self-installing, so WASI R2R can be enabled without specialized splice/probe tooling and CoreCLR hosts no longer install payloads by hand.
Previously the WebCIL payload and function table were passive segments that the host copied in by calling
getWebcilPayloadandfillWebcilTable, which also meant the WASI publishing flow needed extra tooling to splice those segments into the host module. With this change every CoreCLR image carries active segments placed at host-supplied base globals, so the engine installs the payload and table slice at instantiation.Wrapper format: version 2
This introduces WebCIL wrapper version 2 (the
webcilVersionglobal); the payload header itself is unchanged at version 1.(global.get __memory_base).(global.get __table_base).payloadSize(andtableSizefor R2R), so hosts can reserve the aligned memory and table ranges before instantiation.getWebcilSize, pluspatchWebcilHeaderfor R2R images. It writes the header'sTableBasefield, which the runtime reads from linear memory and no segment can supply.getWebcilPayloadandfillWebcilTableare removed.Changes
WebCilObjectWriter): always emits active payload/element segments and the two stubs; passive-only encoding paths removed.WebcilWasmWrapper,WebcilConverter): emits the version 2 wrapper for version 1 payloads (CoreCLR) and keeps the passive version 0 wrapper for Mono.__memory_base, and callpatchWebcilHeaderfor R2R images. Allocation from boot-configpayloadSize/tableSizeand streaming instantiation are unchanged, and neither host parses the module layout before instantiation.WebcilReaderand the R2RWebcilImageReaderaccept active payloads, including the memory-indexed active form (flag 2).docs/design/mono/webcil.mddocuments wrapper version 2, the host ABI, and the matching-artifact contract.This is the focused prerequisite for enabling WASI R2R without the previous custom splice/probe pipeline. #133265's WASI composer currently tells component stubs from composites by passive vs. active segments, so it needs a small follow-up.
Validation
clr.crossarchtools+libs.sfxprerequisites, thenclr.r2rtests(browser-wasm):WasmCompositeModuleandWebcilSegmentAlignmentpass. They check active payload/table placement, exact size and table-index metadata, exports, the wrapper version, alignment, and the emitted component stubs.WebcilInWasmSizesTests(22 tests): converter output for wrapper versions 0 and 2, active payloads with memory indices 0 and 128 (flag 2), metadata and MVID extraction, and stale prebuilt-image rejection.tsc, ESLint, and Release Rollup bundles pass.wasm-tools validateand instantiate through the host sequence in Node: the installed payload is byte-identical, the table slice is filled, andTableBaseis patched.Note
This PR description was drafted with GitHub Copilot.