Skip to content

Wasm backend miscompiles a #valtype struct with a Double first field returned from a raise function (i32.wrap_i64 expected i64, found f64) #1274

Description

@mizchi

Summary

On the wasm target, a #valtype struct whose first field is a Double, when returned from a raise-capable function, is miscompiled: moonc emits an i32.wrap_i64 that consumes the struct's f64 slot, producing invalid Wasm that fails validation at instantiation:

CompileError: WebAssembly.Module(): Compiling function "..._run" failed:
  i32.wrap_i64[0] expected type i64, found local.get of type f64

native, wasm-gc, and js all compile and run the same code correctly — it is wasm-specific.

Minimal reproduction (self-contained, builtin types only)

moon.mod.json

{ "name": "repro", "version": "0.1.0" }

moon.pkg.json

{}

top.mbt

#valtype
priv struct W {
  value : Double
}

priv suberror E

fn make(i : Int64) -> W raise E {
  if i < 0L {
    raise E
  }
  { value: i.to_double() }
}

fn run(i : Int64) -> Double {
  match (try? make(i)) {
    Ok({ value }) => value
    Err(_) => -1.0
  }
}

test {
  ignore(run(123L))
}

Run:

moon test --target wasm

Trigger conditions

All three are required:

  1. the struct is annotated #valtype,
  2. its first field is a Double (f64), and
  3. it is produced by a function that can raise (the Result-of-struct lowering).

The struct's other fields are irrelevant — even a single Double field reproduces it, and a non-Double first field (any reference, Option, or plain Int) does not. It is not about reference/Option fields; in the original @json case the StringView? second field was incidental.

variant result
{ value : Double } (single Double field) FAIL
{ value : Double; other : Int } (two scalars) FAIL
{ value : Double; repr : StringView? } (Double first) FAIL
{ repr : StringView?; value : Double } (Double not first) PASS
{ a : Int; value : Double } (Int first) PASS
remove #valtype (plain boxed struct) PASS
remove raise / try? (no exceptions) PASS

Construction-and-return alone triggers it (no field read / destructure needed), as long as the producer is raise-capable.

Workarounds

  • order the fields so the first field is not a Double, or
  • drop #valtype, or
  • make the producer non-raise.

Toolchain

moon 0.1.20260618 (afb4494 2026-06-18)
moonc v0.10.1+a46be2066 (2026-06-22)
moonrun 0.1.20260618 (afb4494 2026-06-18)

Found while replacing a (Double, StringView?) tuple with a #valtype struct in @json number lexing (moonbitlang/core#3639); the field-order workaround is in use there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions