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

Closed

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:1120 — br_table case-target depth (BBJ_SWITCH).
  • codegenwasm.cpp:3969 — findTargetDepth(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.

Activity

  1. added
    arch-wasmWebAssembly architecture
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Jul 23, 2026
  2. dotnet-policy-service commented on Jul 23, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
    See info in area-owners.md if you want to be subscribed.

  3. dotnet-policy-service commented on Jul 23, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  4. removed
    untriagedNew issue has not been triaged by the area owner
    on Jul 23, 2026
  5. added this to the 11.0.0 milestone on Jul 23, 2026
  6. lewing commented on Jul 23, 2026

    @lewing
    MemberAuthor

    Very likely fixed by #131279, which addresses the underlying wasm block-layout defect.

    Root cause: FgWasm::VisitWasmSuccs attaches a throw helper to a try side-entry via the purely-lexical bbInTryRegions enclosing-try match (added in #130945) without checking that the helper and the side-entry live in the same function region (funclet). That pulls a main-method throw helper into a handler funclet's RPO, which both drops the funclet's terminal end and miscomputes branch-target depths — the ~0 depth reported here. #131279 gates that attach on same-funclet ownership.

    Evidence: System.Threading.Tasks.Parallel (the module in this report) is invalid on baseline crossgen and validates with #131279, in a 194-assembly standalone survey with 0 regressions (details on the PR). Suggest closing this as resolved-by #131279 once it merges; happy to keep it open until then to confirm on the exact Parallel.Invoke repro.

    Note

    This comment was authored with the assistance of GitHub Copilot.

  7. locked and limited conversation to collaborators on Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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