Skip to content

rv32 selector: i64 local.tee rejected (stack type mismatch) — no RV32 path for packed-u64 decide returns #312

Description

@avrabe

RV32 selector rejects i64 local.tee — blocks every packed-u64 verified-decide primitive on the RISC-V lane

Found while scope-checking #311 across backends (same 15-line repro, u64repro.c):

synth compile u64.loom.wasm -b riscv -t rv32imac --all-exports --relocatable
warning: skipping function 'check': backend 'riscv' failed: compilation failed:
  RISC-V selector: stack type mismatch at op LocalTee(2): expected i32, found i64 on top of stack
Error: no functions compiled successfully (1 skipped) — nothing to emit

The same module compiles clean on --target cortex-m4f (where it then hits #311's silent wrong-code instead). So the backend split on packed-u64 returns is currently:

backend i64 unpack
ARM compiles, silently wrong (#311, since v0.11.18)
RV32 refuses to compile (this issue) — fail-safe, but a hard blocker

To the RV32 selector's credit, failing loudly is the right ASIL posture — but it means the entire verified-decide pattern (Rust decide functions returning action/value packed in a u64 — sem, mutex, every gale primitive) has no working RV32 path. The wasm is ordinary clang -O2 output: local.tee of an i64 temp during the shift/mask unpack.

Ask: i64 local.tee (and presumably the sibling i64 local ops) in the RV32 selector — register-pair (a-reg pair) handling like the existing i64 arithmetic. Repro staged (u64repro.c → wasm in one command); the in-repo u64_funccheck.py lane can grow an RV32 leg (qemu/unicorn differential) the moment it compiles — same-day verification as usual.

Context: surfaced by #311's backend scope-check; the two issues together gate the sem/mutex re-baselines on both lanes.

Activity

  1. avrabe commented on Jun 10, 2026

    @avrabe
    ContributorAuthor

    Acknowledged — scheduled as the immediate next release (v0.11.37) after v0.11.36 tags (which carries the ARM-lane #311 fix + #237 + the realloc work; PRs #309/#310 are in CI now).

    Scope confirmed from the error: the RV32 selector's i64 local ops are the gap (#223 added 4-byte non-param local slots; i64 locals were the documented follow-up). The fix is the pair-analogue of what #311 just landed on ARM:

    Your repro compiles leaf (loom-inlined) after the local work; check_call additionally needs the RV32 non-leaf path (ra + caller-saved across calls — the ARM-#188 analogue, still open on RV32), which I'll scope in the same pass and either include or split honestly. The funccheck RV32 leg lands with it. Same-day turnaround target once v0.11.36 is out the door.

  2. avrabe commented on Jun 11, 2026

    @avrabe
    ContributorAuthor

    Implemented in PR #316 (rides as v0.11.37): i64 locals (8-byte slots, two-word lw/sw, a0:a1 call-result pairs via the shared #311 result tables) plus what the new behavioral oracle caught underneath — a latent per-op register-aliasing hole in the lowest-free allocator (#231 class): back-to-back alloc_temp calls within one op's expansion returned the same register, collapsing every i64 pair. Fixed with per-op pin scope. Your exact failure mode (local.tee of the loom-inlined u64) is the committed RV32 unicorn lane: 4/4 PASS vs wasmtime (failed 4/4 compile-only). Your two-lane table closes: ARM correct (v0.11.36), RV32 correct (this PR). Honest scope notes: i64 params stay fail-loud-Unsupported (psABI pair convention is its own increment); check_call-style cross-function shapes pass the selector but still skip at ELF emit (the pre-existing RV32 call-relocation gap, ARM-#167 analogue); and the oracle surfaced a pre-existing i64.div_s sign-clobber — filed separately with probe evidence.

  3. avrabe commented on Jun 11, 2026

    @avrabe
    ContributorAuthor

    v0.11.37 is tagged and live — https://github.com/pulseengine/synth/releases/tag/v0.11.37 (crates.io publish completing). The RV32 funccheck lane has its target: i64 locals + the per-op pin-scope fix, with the committed u64_unpack_riscv_differential.py as the reference oracle (4/4 vs wasmtime). Both lanes of your table are now released: ARM v0.11.36, RV32 v0.11.37. Known + filed: #317 (i64.div_s signs, pre-existing, probe staged) — flag if jess's modules divide signed i64s and it jumps the queue.

  4. avrabe commented on Jun 11, 2026

    @avrabe
    ContributorAuthor

    v0.11.37 verified: RV32 compiles AND computes the repro correctly — #312 can close

    synth compile u64repro -b riscv -t rv32imac on v0.11.37: 328 B object (was the hard LocalTee i64 rejection), and under unicorn RISCV32 (s11=0, a0:a1 pairs) all four vectors compute correctly:

    PASS check(3,4):     wasm=8     arm=8     rv32=8
    PASS check(0,0):     wasm=1     arm=1     rv32=1
    PASS check(1000,99): wasm=1100  arm=1100  rv32=1100
    PASS check(65535,1): wasm=65537 arm=65537 rv32=65537
    

    The in-repo u64_funccheck.py lane is now the promised three-way differential (wasmtime / ARM-unicorn / RV32-unicorn) — every future release gets both backends of the packed-u64 decide pattern checked automatically. The backend table from the issue body ends as designed: both lanes correct.

    With the ARM side silicon-verified at 860 cyc (#311 thread), the verified-decide pattern is fully unblocked on both targets. From gale's side #312 can close.

  5. avrabe commented on Jun 11, 2026

    @avrabe
    ContributorAuthor

    Closing on your verification — RV32 4/4 with the three-way (wasmtime/ARM/RV32) funccheck lane now standing for every future release. The backend table ends as designed: both lanes correct, both fail-loud where unimplemented (#317 div_s is the known boundary, queued).

  6. added a commit that references this issue on Aug 19, 2026
  7. added 2 commits that reference this issue on Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions