Skip to content

codegen: lower match to scrutinee-save + chained i64.eq + nested if (closes #43)#61

Merged
proofmancer merged 1 commit into
mainfrom
codegen/match
May 25, 2026
Merged

codegen: lower match to scrutinee-save + chained i64.eq + nested if (closes #43)#61
proofmancer merged 1 commit into
mainfrom
codegen/match

Conversation

@proofmancer

Copy link
Copy Markdown
Contributor

Closes #43.

Cleave-compiled WASM modules can pattern match now. The v0.3 codegen rejected match with the diagnostic "statement kind StmtMatch not yet supported in v0 codegen"; this PR lifts that. Builds directly on the if/else lowering from #49.

What landed

Codegen

  • emit_match saves the scrutinee to a shared scratch local, then walks arms recursively. Each non-wildcard arm becomes local.get scratch; <pattern>; i64.eq; if 0x40 ... else ... end. Wildcard arms (any identifier in pattern position; v0 treats _ and bound names alike) terminate the chain as the innermost else branch.
  • Supported patterns: integer literal, bool literal, identifier as wildcard. Constructor patterns (Ok(x), Some(x)) need sum types (Sum types end-to-end: Result, Option, user-declared enums #48); binding patterns need typecheck integration (deferred).
  • Match scratch local. The fn-body pre-scan detects any match statement and allocates one shared anonymous local for the scrutinee. Matches are sequential within a fn, so one scratch covers all of them.
  • count_local_slots helper replaces count_let_bindings; reports both let count and match presence. emit_fn_body translates that into a single locals declaration group.
  • leaves_value_on_stack extended to recurse into block result, so a match arm body that's a block ending in an assignment correctly skips the post-arm drop.

Tests (5 new, byte-level)

  • match allocates exactly one extra i64 local in the locals declaration
  • scrutinee-save-then-compare byte sequence (local.get 0; local.set 1; local.get 1; i64.const N; i64.eq; if 0x40)
  • wildcard terminates chain (2 if 0x40 substrings for 3 arms with wildcard)
  • no-wildcard omits final else (look for call 1; end; end substring)
  • wildcard-only match emits no if byte (just the unconditional body)

Bench

codegen_match_heavy exercises 6 literal arms + wildcard. ~226K ops/sec on M3 Pro, the heaviest of the codegen benches.

End-to-end demo

module MatchDemo {
    state count: u64

    fn route(code: u64) -> u64 {
        match code {
            1 => count = 100,
            2 => count = 200,
            3 => count = 300,
            _ => count = 999
        }
        count
    }
}

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

$ cleave-run /tmp/match.wasm route 1     # 100
$ cleave-run /tmp/match.wasm route 2     # 200
$ cleave-run /tmp/match.wasm route 3     # 300
$ cleave-run /tmp/match.wasm route 42    # 999 (wildcard arm)

Disassembly shows exactly the lowering described:

(func (param i64) (result i64)
  (local i64)
  local.get 0     ;; scrutinee
  local.set 1     ;; -> scratch
  local.get 1     ;; arm 1
  i64.const 1
  i64.eq
  if              ;; match 1
    ...x = 100...
  else
    local.get 1   ;; arm 2
    i64.const 2
    i64.eq
    if            ;; match 2
      ...x = 200...
    else
      local.get 1 ;; arm 3
      i64.const 3
      i64.eq
      if          ;; match 3
        ...x = 300...
      else
        ...x = 999...  ;; wildcard
      end
    end
  end
  i32.const 0
  call 0)         ;; return count

What this PR does NOT yet do

  • Binding patterns (match opt { Some(x) => use(x), None => default() }): identifier patterns are wildcards today. Real binding needs:
  • Constructor patterns (Ok(x), Cons(head, tail)): same blocker.
  • Guard arms (1 if x > 0 => ...): not in the parser.
  • Match as expression: parser is statement-only today.
  • Exhaustiveness checking: an unmatched scrutinee with no wildcard is a silent no-op in v0. Worth a type-checker upgrade once enum types exist.

What unblocks next

@proofmancer proofmancer merged commit f776679 into main May 25, 2026
2 checks passed
@proofmancer proofmancer deleted the codegen/match branch May 25, 2026 14:09
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: match statements

1 participant