[wasm][R2R] crossgen2 emits function types exceeding the 1000-parameter wasm limit, silently disabling R2R for the assembly
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
`crossgen2` targeting wasm can emit a function type whose parameter count exceeds the **1000-parameter implementation limit**, producing an R2R image that no engine will instantiate. The runtime catches the failure and silently falls back to interpreting that assembly, so the only symptom is a log line and a large, invisible loss of R2R coverage.
This is the CoreCLR/crossgen2 recurrence of a bug already found and fixed on the Mono side:
- [#76719](https://github.com/dotnet/runtime/issues/76719) — *"Wasm AOT can produce functions with too many parameters"* (2022, closed). Same root cause, and it names the same trigger file. It explicitly recommends *"detect it during the build process and either not AOT the method or fail the build."*
- [#80243](https://github.com/dotnet/runtime/pull/80243) — *"[wasm] Disable llvm for methods/calls with more than 1000 parameters"* — the Mono fix.
crossgen2 has no equivalent guard.
**The limit is documented and not V8-specific.** V8 enforces max 1000 params (also 1000 results, 50 000 locals, 65 520 `br_table` entries). `wasmtime` and `wasm-tools` enforce the same parameter cap, so the emitted module is invalid for any conforming engine, not merely unloadable in browsers.
### Reproduction Steps
Build a browser-wasm R2R image over the `System.Text.Json` test closure:
```bash
./build.sh clr+libs -os browser -c Release
./dotnet.sh build src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj \
/p:TargetOS=browser /p:TargetArchitecture=wasm /p:RuntimeFlavor=CoreCLR \
/p:Configuration=Release /p:TargetFramework=net11.0 /p:PublishReadyToRun=true
```
Then run it (flat directory, `corerun.js`, per-assembly R2R images deployed beside the app):
```bash
DOTNET_ReadyToRun=1 APP_ASSEMBLIES=EXTERNAL \
node --experimental-wasm-exnref --stack-size=8192 corerun.js -c "$PWD" \
WasmTestRunner.dll System.Text.Json.Tests.dll
```
Reproduces with **composite** (`crossgen2 --composite --targetos:browser`) as well — it is not specific to either mode.
**The trigger** is `ClassWithManyConstructorParameters` in
[`src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs`](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs) (~line 2559), whose constructor takes `int P000 … int P999` — exactly 1000 managed parameters. Lowered to wasm that becomes **1003** params (1000 managed + 3 ABI), which is over the cap.
This is the same test type named in #76719.
### Expected behavior
`crossgen2` declines to compile a method whose lowered wasm signature would exceed the implementation limit, leaving it to the interpreter/JIT — the behaviour #80243 gave Mono. Ideally it also emits a diagnostic rather than failing silently at load.
### Actual behavior
The emitted module contains a `functype` with 1003 `i32` params. In the composite image the offending type sits at file offset `0x187B` (`60 eb 07` — `0x60` functype, LEB `0xeb 0x07` = 1003, followed by 1003 × `0x7f`).
Every engine rejects it:
| engine | 1000 params | 1003 params |
| --- | --- | --- |
| Chrome 152.0.7977.65 | accepted | `WebAssembly.Module(): param count of 1003 exceeds internal limit of 1000` |
| node 26.4.0 (V8) | accepted | same message |
| wasmtime 45.0.2 | — | `function params size is out of bounds` |
| `wasm-tools parse` | accepted | `function params size is out of bounds` |
At runtime this surfaces as:
```
Failed to construct WebAssembly module for Webcil image: {
wasmPath: 'System.Text.Json.Tests.wasm',
errorMessage: 'WebAssembly.Module(): param count of 1003 exceeds internal limit of 1000 @+1403'
}
```
and then, in `DOTNET_ReadyToRunLogFile` output:
```
Ready to Run header not found: "System.Text.Json.Tests".
Ready to Run initialized successfully: "System.Text.Json".
```
i.e. the assembly is silently downgraded to interpreted while the rest of the closure stays R2R. **The run otherwise proceeds normally**, so without checking the R2R log there is no indication that an assembly lost R2R entirely.
### Regression?
Not a regression in CoreCLR — this is the first time the wasm R2R path has been exercised over this test closure, as far as I can tell. It is a recurrence of a known Mono-side defect (#76719) in a different compiler.
### Known Workarounds
None at the crossgen2 level. The affected assembly runs interpreted, which is correct but silently costs all R2R coverage for it.
### Configuration
- `dotnet/runtime` `main` (plus an in-progress WASI R2R integration delta; the affected code paths are unmodified from `main`).
- Target: `browser-wasm`, `RuntimeFlavor=CoreCLR`, `Configuration=Release`.
- Host: macOS arm64; engines as listed in the table above.
### Other information
Adjacent limits worth auditing in crossgen2's wasm backend, since they have the same shape and the same silent-failure mode — V8 caps **returns at 1000**, **locals at 50 000**, and **`br_table` entries at 65 520**. The `br_table` one may be worth checking first: wasm R2R already emits `br_table` for `TypeCode`-style dispatch.
Related wasm R2R validity issues, all closed, suggesting module-validity checking is worth doing systematically rather than case by case: #129335, #129743, #129526.
> [!NOTE]
> This issue was drafted with GitHub Copilot (AI-generated) and reviewed by the author.
Contributor guide
Research direction
Start by reproducing the failure with the browser-wasm R2R build commands and the System.Text.Json test closure. Inspect the crossgen2 wasm backend and the ClassWithManyConstructorParameters case in src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs. Done means an over-limit lowered signature is declined or diagnosed, and the affected assembly no longer produces an invalid R2R image.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- wasm
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100