Skip to content

[wasm] crossgen2 ships an invalid branch depth (~0) for EH funclets whose branch target isn't on the wasm control-flow stack #131252

Description

@lewing

Description

When crossgen2 compiles managed methods to WebAssembly (wasm32) R2R, CodeGen::findTargetDepth can fail to locate a branch's target block in the active wasm control-flow stack. In that case it falls through to a checked-only assert and then return ~0;. Because the assert is compiled out of release/optimized crossgen2, the sentinel ~0 (0xFFFFFFFF) is emitted as the branch depth of a br / br_if / br_table instruction, producing an invalid wasm module. Validators reject it — V8 reports invalid branch depth: 4294967295.

Relevant source (src/coreclr/jit/codegenwasm.cpp); line numbers are relative to main and will drift:

unsigned CodeGen::findTargetDepth(BasicBlock* targetBlock)   // codegenwasm.cpp:517
{
    // ... searches wasmControlFlowStack for targetBlock ...

#ifdef DEBUG
    JITDUMP("Could not find " FMT_BB "[%u]%s in active control stack\n", ...);
    // ... dumps the current stack ...
#endif

    assert(!"Can't find target in control stack");   // codegenwasm.cpp:568 — DEBUG-only

    return ~0;                                        // codegenwasm.cpp:570 — release ships 0xFFFFFFFF
}

The two callers feed this depth straight into emitted branch instructions:

  • codegenwasm.cpp:1120br_table case-target depth (BBJ_SWITCH).
  • codegenwasm.cpp:3969findTargetDepth(tgtBlock) + wasmExtraControlFlowDepth for an emitted br/br_if.

So a ~0 return does not merely misbehave in one spot — it is written into the module's branch encoding.

Observed on the exception-handling funclet System.Threading.Tasks.Parallel.Invoke (mangled Invoke_0, funclet 1) during a full framework wasm R2R crossgen. It is not SIMD-related (the funclet contains no 0xfd-prefixed opcodes).

This is a distinct root cause from the terminal-end drop fixed in #131251 (that fix addresses funclet end-emission; this is funclet branch-target resolution), but it is the same family of defect: an EH-funclet wasm-codegen gap guarded only by a checked-only assert, so in release the malformed wasm ships silently instead of failing the build.

Reproduction Steps

  1. Build a wasm R2R crossgen2 (release) and a browser-wasm framework pack.
  2. Crossgen a framework assembly (or composite) containing System.Threading.Tasks.Parallel for wasm:
    crossgen2 -O --targetarch:wasm --targetos:browser \
      --codegenopt:JitWasmNyiToR2RUnsupported=1 \
      -r:<every other framework dll> \
      -o:out.wasm <assembly containing System.Threading.Tasks.Parallel>.dll
    
  3. Validate the emitted module:
    wasm-tools validate out.wasm      # or load it in V8 / a browser
    

The malformed function is the Parallel.Invoke EH funclet; V8 rejects the whole module with invalid branch depth: 4294967295.

Expected behavior

crossgen2 emits a valid wasm module: every emitted br / br_if / br_table carries a real, in-range control-stack depth for its target block. If a target genuinely cannot be resolved on the control-flow stack, crossgen2 should fail the compilation (or mark the method R2R-unsupported) rather than silently emit a ~0 branch depth into shipping wasm.

Actual behavior

findTargetDepth fails to find the target block on the wasm control-flow stack for certain EH-funclet shapes. In release the DEBUG assert is gone, so it returns ~0 and that value is encoded as the branch depth. The resulting module fails validation:

invalid branch depth: 4294967295

(V8; wasm-tools/other validators reject it equivalently.)

Regression?

Not a regression from a shipping release — wasm R2R (wasm32 crossgen2) is in-development. This is within the same wasm EH-funclet codegen area as #129335, #129449, and #131251.

Known Workarounds

None at the module level once emitted — unlike the terminal-end drop (#131251), the missing/incorrect data is a branch-target depth that cannot be repaired by appending a byte. Avoiding the affected funclet shape (e.g. excluding the offending method from R2R) sidesteps it.

Configuration

  • .NET 11 (main), CoreCLR wasm R2R (crossgen2), target wasm32 / --targetos:browser.
  • Host: macOS/arm64; reproduced against a browser-wasm framework pack.
  • Not specific to SIMD codegen (the funclet has no SIMD opcodes).

Other information

Note

This issue was authored with the help of GitHub Copilot.

Metadata

Metadata

Assignees

Labels

arch-wasmWebAssembly architecturearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions