Skip to content

[wasi] Composite R2R images export every function, exceeding the engine type-size limit at framework scale #132905

Description

@lewing

Description

WebCilObjectWriter.WriteExports exports every defined function in a wasm R2R image, under a mangled managed name, and crossgen2 emits no wasm name section. At framework scale this produces a module that no conforming engine will load.

https://github.com/dotnet/runtime/blob/main/src/coreclr/tools/Common/Compiler/ObjectWriter/WebCilObjectWriter.cs#L992-L1007

// TODO-WASM: Handle exports better (e.g., only export public methods, etc.)
IEnumerable<WasmSymbol> functionSymbols = _wasmSymbolManager.GetDefinitions(
    WasmIndexSpace.Function,
    Comparer<WasmSymbol>.Create(static (x, y) => x.Name.CompareTo(y.Name)));
foreach (WasmSymbol symbol in functionSymbols)
{
    WriteFunctionExport(symbol.Name.ToString(), symbol.Index);
}

The existing TODO-WASM already flags this. This issue adds the measurement showing it is now a hard blocker rather than a tidiness item, and notes that the original justification no longer applies.

The V0 object writer (#122111) exported function bodies "so that the resulting module can be loaded and method bodies can be called by an external loader." That is no longer what makes them callable — WriteElements(), directly below WriteExports(), already emits an element segment over all function indices, which is what populates the dispatch table.

Reproduction Steps

Compile a composite over the full wasi-wasm framework closure (181 assemblies) and validate it:

crossgen2 --composite -O --targetarch:wasm --targetos:wasi \
    --instruction-set base,simd128 \
    --codegenopt:JitWasmNyiToR2RUnsupported=1 \
    --codegenopt:JitWasmSimdNyiToR2RUnsupported=1 \
    --out composite-r2r.wasm \
    <System.Private.CoreLib.dll + the 180 pack assemblies>

wasm-tools validate --features all composite-r2r.wasm
wasmtime  compile  composite-r2r.wasm -o /dev/null

Note the output is ~127 MB; a much smaller result means the inputs were not passed as expected rather than that the bug is absent.

Expected behavior

The composite validates and loads.

Actual behavior

Both tools reject it with the identical message (both use wasmparser):

error: effective type size exceeds the limit of 1000000 (at offset 0x1371443)

The offset falls inside the Export section. Section layout:

section count size
Type 634 6.6 KB
Function 228,624 322 KB
Export 228,626 28.4 MB
Code 228,624 87 MB

The type section is not implicated — the largest function type has 67 parameters and the sum of 1 + params + results across all 634 types is 5,332.

Removing the export section (leaving everything else byte-identical) makes the same image valid, and shrinks it from 133.4 MB to 105 MB. A 4-assembly composite (52,617 functions) validates fine, so the limit falls between the two; the failure offset implies a ceiling around ~160,000 exported functions.

EH funclets are exported as separate functions (..._funclet_0, ..._funclet_1, added in #128736), which inflates the count further.

Known Workarounds

Strip the export section post-crossgen2. The image then validates, and R2R code still provably executes — verified with a breakpoint on an R2R method body in a spliced WASI host, which halts with R2R frames on the stack.

The cost is that all symbol names are lost. Because crossgen2 emits no custom sections at all, the export names are the only source of function names; wasm-merge -g synthesizes the merged module's name section from them. With exports stripped, every composite frame is anonymous in a debugger.

So today the export section is doing three jobs, and you can only keep two: symbol names, historical callability (now redundant), and staying under the engine limit.

Other information

Suggested direction: emit a name section instead of exporting every function. A name section is a custom section — engines ignore it, it does not count toward the effective-type-size limit, and it can be stripped for size without affecting behavior. That would lift the ceiling, preserve debuggability, and shrink the image, where today those are mutually exclusive. WriteElements() already provides callability.

Same family as #132855 (wasm parameter-count limit): a crossgen2 wasm emission that exceeds a hard engine limit at scale and produces an image that cannot be loaded.

Configuration

  • main @ 3174387aeda, crossgen2 targeting wasm/wasi, Release
  • wasm-tools 45, wasmtime 45.0.2, host osx-arm64
  • Not target-OS specific: the export emission is shared, so a browser composite of the same closure should reject identically.

Regression?

No — present since the wasm object writer was introduced. Only reachable at framework-scale composites, which is why it has not surfaced before.

Note

This issue was drafted with GitHub Copilot (AI-generated) and reviewed by the author.

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

  • Status
    No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions