Skip to content

crossgen2 wasm: invalid catch emitted for async/await state machine (R2R module fails wasm validation) #129743

Description

@pavelsavara

Description

crossgen2's WebAssembly backend emits an invalid catch instruction for the C# async/await state machine. When an assembly containing any async method is cross-compiled to ReadyToRun for browser-wasm, the resulting R2R wasm module fails wasm decoding/validation, so the browser (and V8/d8/Node) rejects it at module-compile time:

CompileError: WebAssembly.Module(): Compiling function #N failed:
catch kind generates 1 operand, target block expects 0

The plain IL build of the same code runs fine — only the R2R image is malformed.

This is the wasm exception-handling lowering of the compiler-generated MoveNext: the C# compiler wraps the async method body in a try/catch (to forward the exception to AsyncTaskMethodBuilder.SetException), and crossgen2 lowers that catch to a wasm catch whose operand arity doesn't match the enclosing block's expected type.

Minimal repro

A single async method is enough — no user-level try/catch is required:

using System.Runtime.CompilerServices;
using System.Threading.Tasks;

public static class Test
{
    public static async Task<int> Main()
    {
        return await AsyncYield(1);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static async Task<int> AsyncYield(int input)
    {
        await Task.Yield();
        return input * 2;
    }
}

Cross-compile to R2R for wasm, then load the produced module in any wasm engine.

The crossgen2 invocation used (from the in-tree WebAssembly SDK R2R hook):

crossgen2 @<rsp> --targetarch:wasm --targetos:browser --obj-format:wasm \
    --jitpath:<clrjit_wasm> --codegenopt:JitWasmNyiToR2RUnsupported=1 -O \
    --out:<app>.wasm <app>.dll

Validate the raw crossgen output directly (no runtime needed — it's a decode/validation failure) with d8/V8:

const bytes = new Uint8Array(readbuffer("App.wasm"));
try { new WebAssembly.Module(bytes); print("COMPILE OK"); }
catch (e) { print("COMPILE FAILED: " + e); }

Observed:

COMPILE FAILED: CompileError: WebAssembly.Module(): Compiling function #5 failed:
catch kind generates 1 operand, target block expects 0 @+4435

(V8 15.1.103.)

What works vs. what fails

Cross-compiled to R2R for wasm, the following synchronous EH shapes all validate and run fine:

  • plain try/catch
  • try/finally
  • try/catch/finally
  • catch (…) when (filter)
  • throw; rethrow
  • nested try/catch
  • catch { } (no exception variable)

Only the async state machine MoveNext produces the invalid catch.

Expected

crossgen2 should emit a valid wasm catch for the async-state-machine try/catch, so R2R images containing async methods load in wasm engines.

Actual

The R2R wasm module fails to compile/validate with catch kind generates 1 operand, target block expects 0.

Environment

  • dotnet/runtime main
  • Target: browser-wasm, CoreCLR, Debug
  • crossgen2 wasm backend (--obj-format:wasm, clrjit wasm codegen)

Note

This issue was authored with the assistance of GitHub Copilot.

Activity

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 SuperPMIos-browserBrowser variant of arch-wasm

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions