Skip to content

codegen: lower if/else/else-if to WASM if instruction (closes #49)#60

Merged
proofmancer merged 1 commit into
mainfrom
codegen/if-else
May 25, 2026
Merged

codegen: lower if/else/else-if to WASM if instruction (closes #49)#60
proofmancer merged 1 commit into
mainfrom
codegen/if-else

Conversation

@proofmancer
Copy link
Copy Markdown
Member

Closes #49.

Cleave-compiled WASM modules can branch now. The v0.3 codegen rejected if statements with the diagnostic "statement kind StmtIf not yet supported in v0 codegen"; this PR lifts that.

What landed

Codegen

  • New emit_if lowers if cond { ... }, if cond { ... } else { ... }, and else if chains to WASM's native if 0x40 ... else ... end instruction
  • Empty block type for now: if-as-expression remains gated on the parser supporting if at expression position
  • else if chains nest naturally (an AST_STMT_IF in the else slot just recurses into emit_if)
  • New leaves_i32_on_stack helper distinguishes comparison ops (already return i32 per WASM spec) from everything else (i64). Codegen emits i32.wrap_i64 (0xA7) only when the cond expression leaves i64, avoiding redundant coercions on comparison conditions.
  • New leaves_value_on_stack helper that returns false for assignments. Used by both emit_if_branch (don't drop after an assignment branch) and emit_fn_body (don't suppress the synthetic default-zero when the trailing expression is an assignment). This fixes a latent bug: a fn body ending in x = expr was producing invalid WASM before this PR. No example hit the path so the v0.3 release had it silently broken.

Opcodes added in wasm.h

WASM_OP_BLOCK 0x02, WASM_OP_LOOP 0x03, WASM_OP_IF 0x04, WASM_OP_ELSE 0x05, WASM_OP_I32_WRAP_I64 0xA7. New constant WASM_BLOCKTYPE_EMPTY 0x40.

Tests (6 new, byte-level)

  • if without else
  • if with else
  • else-if chain (asserts two if 0x40 substrings in the output)
  • comparison-cond does not emit redundant i32.wrap_i64
  • bool-literal cond does emit i32.wrap_i64
  • assignment-branch does not emit spurious drop (the bug found during development)

Bench

codegen_if_heavy exercises a 5-way else-if chain. ~249K ops/sec on M3 Pro, slowest of the codegen benches as expected.

End-to-end demo

module IfElse {
    state count: u64

    fn add_if_zero(amount: u64) -> u64 {
        if count == 0 {
            count = amount
        } else {
            count = count + amount
        }
        count
    }

    fn classify(n: u64) -> u64 {
        if n < 10       { count = 1 }
        else if n < 100 { count = 2 }
        else if n < 1000 { count = 3 }
        else            { count = 4 }
        count
    }
}

Compiles cleanly, wasm-validate-clean, runs correctly:

$ cleave-run /tmp/if-else.wasm add_if_zero 50    # fresh state -> count = 50
50
$ cleave-run /tmp/if-else.wasm classify 5         # 5 < 10 -> count = 1
1
$ cleave-run /tmp/if-else.wasm classify 42        # < 100 -> 2
2
$ cleave-run /tmp/if-else.wasm classify 500       # < 1000 -> 3
3
$ cleave-run /tmp/if-else.wasm classify 9999      # else -> 4
4

Debugging note worth flagging

When I first ran the smoke test, wasm-validate failed with "type mismatch in drop, expected [any] but got []" at offsets inside the if branches. Root cause: the parser puts a trailing assignment (the last statement of a block, no semicolon, sitting before }) into block.result rather than block.stmts. My initial emit_if_branch treated branch.result != NULL as "a value is on the stack" and emitted a drop. But an assignment expression leaves nothing on the stack (state_set is a void hostcall; local.set consumes its value). The fix is leaves_value_on_stack, which also fixed the latent v0.3 bug where a fn ending in x = expr produced invalid WASM.

The new CI gates (#59) would have caught this immediately. wasm-validate failed locally on the first compile. Worth recording the value of that gate.

What this PR does NOT yet do

What unblocks next

@proofmancer proofmancer merged commit fe50e5b into main May 25, 2026
2 checks passed
@proofmancer proofmancer deleted the codegen/if-else branch May 25, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codegen: if/else control flow

1 participant