///|

///|
pub enum TcEscape {
  NoTcEscape
  BranchTcEscape(Int32)
  TerminalTcEscape
} derive(Eq, Debug)

///|
pub impl Show for TcEscape with fn output(self, logger) {
  match self {
    NoTcEscape => logger.write_string("NoTcEscape")
    BranchTcEscape(depth) => logger.write_string("BranchTcEscape(\{depth})")
    TerminalTcEscape => logger.write_string("TerminalTcEscape")
  }
}

///|
pub struct TcState {
  env : Env
  stack : Array[ValType]
  // Execution flow is independent of the current validation frame.
  reachable : Bool
  polymorphic : Bool
  escape : TcEscape
  reachable_escape_depths : Array[Int32]
  initialized_locals : Array[Bool]
}

///|
pub impl Show for TcState with fn output(self, logger) {
  logger.write_string("TcState {env: ")
  self.env.output(logger)
  logger.write_string(", stack: ")
  self.stack.output(logger)
  logger.write_string(", reachable: ")
  self.reachable.output(logger)
  logger.write_string(", polymorphic: ")
  self.polymorphic.output(logger)
  logger.write_string(", escape: ")
  self.escape.output(logger)
  logger.write_string(", reachable_escape_depths: ")
  self.reachable_escape_depths.output(logger)
  logger.write_string(", initialized_locals: ")
  self.initialized_locals.output(logger)
  logger.write_char('}')
}

///|
pub type TcResult = Result[TcState, String]

///|
pub trait Typecheck {
  fn typecheck(Self, TcState) -> Result[TcState, String]
}

///|
fn TcState::set_unreachable(st : TcState) -> TcState {
  st.stack.clear()
  if !st.reachable {
    return TcState::{ ..st, polymorphic: true, }
  }
  TcState::{
    ..st,
    reachable: false,
    polymorphic: true,
    escape: TerminalTcEscape,
  }
}

///|
fn TcState::set_branch_escape(st : TcState, depth : Int32) -> TcState {
  st.stack.clear()
  if !st.reachable {
    return TcState::{ ..st, polymorphic: true, }
  }
  TcState::{
    ..st,
    reachable: false,
    polymorphic: true,
    escape: BranchTcEscape(depth),
  }
}

///|
fn tc_escape_depths_has(depths : Array[Int32], depth : Int32) -> Bool {
  depths.any(fn(d) { d == depth })
}

///|
fn tc_escape_depths_shift_to_parent(depths : Array[Int32]) -> Array[Int32] {
  let out : Array[Int32] = []
  for depth in depths {
    if depth > 0 {
      out.push(depth - 1)
    }
  }
  out
}

///|
fn tc_escape_depths_merge(
  left : Array[Int32],
  right : Array[Int32],
) -> Array[Int32] {
  if left.length() == 0 {
    return right
  }
  if right.length() == 0 {
    return left
  }
  let out = left.copy()
  for depth in right {
    out.push(depth)
  }
  out
}

///|
fn tc_escape_depths_merge_children(
  left : Array[Int32],
  right : Array[Int32],
  output_arrays~ : Array[Int32]? = None,
) -> Array[Int32] {
  // The child observations are borrowed. Keep their order and duplicates in
  // one owned result instead of making and then merging two shifted arrays.
  let out : Array[Int32] = []
  if output_arrays is Some(count) {
    count[0] += 1
  }
  for depth in left {
    if depth > 0 {
      out.push(depth - 1)
    }
  }
  for depth in right {
    if depth > 0 {
      out.push(depth - 1)
    }
  }
  out
}

///|
fn TcState::record_reachable_escape_depth(
  st : TcState,
  was_reachable : Bool,
  depth : Int32,
) -> TcState {
  if !was_reachable {
    return st
  }
  let depths = st.reachable_escape_depths.copy()
  depths.push(depth)
  TcState::{ ..st, reachable_escape_depths: depths, }
}

///|
fn TcState::record_reachable_escape_label(
  st : TcState,
  was_reachable : Bool,
  label : LabelIdx,
) -> TcState {
  let LabelIdx(raw) = label
  st.record_reachable_escape_depth(was_reachable, raw.reinterpret_as_int())
}

///|
fn TcState::merge_child_reachable_escape_depths(
  st : TcState,
  child : TcState,
) -> TcState {
  let shifted = tc_escape_depths_shift_to_parent(child.reachable_escape_depths)
  TcState::{
    ..st,
    reachable_escape_depths: tc_escape_depths_merge(
      st.reachable_escape_depths,
      shifted,
    ),
  }
}

///|
fn TcState::push1(st : TcState, vt : ValType) -> TcState {
  st.stack.push(vt)
  st
}

///|
fn tc_initialized_intersection(
  left : Array[Bool],
  right : Array[Bool],
  element_checks~ : Array[Int32]? = None,
) -> Array[Bool] {
  if physical_equal(left, right) {
    // A read-only control fork can share its mask with both arms. The merged
    // state still owns a copy so a later initialization cannot change either arm.
    return left.copy()
  }
  let limit = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  match element_checks {
    Some(counts) => counts[0] += limit
    None => ()
  }
  Array::makei(limit, fn(idx) { left[idx] && right[idx] })
}

///|
#inline
fn tc_initialized_join(left : Array[Bool], right : Array[Bool]) -> Array[Bool] {
  // Expression owners copy inherited masks before changing a false bit.
  // Reusing an unchanged join also keeps enclosing joins on this constant-time
  // path, instead of making equal-but-distinct masks at every nested if.
  if physical_equal(left, right) {
    left
  } else {
    tc_initialized_intersection(left, right)
  }
}

///|
priv struct TcInitializationOwner {
  mut mask : Array[Bool]?
  copy_count : Array[Int32]?
}

///|
fn TcInitializationOwner::new(
  copy_count~ : Array[Int32]? = None,
) -> TcInitializationOwner {
  { mask: None, copy_count, }
}

///|
fn TcState::mark_local_initialized(
  st : TcState,
  idx : LocalIdx,
  initialization_owner~ : TcInitializationOwner? = None,
) -> TcState {
  let LocalIdx(raw) = idx
  let local_id = raw.reinterpret_as_int()
  if local_id >= 0 &&
    local_id < st.initialized_locals.length() &&
    !st.initialized_locals[local_id] {
    // Control entry, sibling arms, and prior states may share this mask. A
    // semantic change owns its storage before setting a bit; idempotent writes
    // leave every shared mask untouched.
    if initialization_owner is Some(owner) {
      if owner.mask is Some(owned) &&
        physical_equal(owned, st.initialized_locals) {
        st.initialized_locals[local_id] = true
        return st
      }
      if owner.copy_count is Some(count) {
        count[0] += 1
      }
    }
    let initialized_locals = st.initialized_locals.copy()
    if initialization_owner is Some(owner) {
      owner.mask = Some(initialized_locals)
    }
    initialized_locals[local_id] = true
    return TcState::{ ..st, initialized_locals, }
  }
  st
}

///|
fn TcState::pop_ref_or_bot(st : TcState) -> Result[(TcState, RefType?), String] {
  match st.pop1() {
    Err(e) => Err(e)
    Ok((st1, v)) =>
      match v {
        BotValType => Ok((st1, None)) // unknown ref type, but acceptable
        RefTypeValType(rt) => Ok((st1, Some(rt)))
        _ => Err("expected a reference operand")
      }
  }
}

///|
fn TcState::check_branch_args_present(
  st : TcState,
  l : LabelIdx,
) -> Result[Array[ValType], String] {
  match st.env.get_label_types(l) {
    None => Err("invalid label index")
    Some(ts) => Ok(ts)
  }
}

///|
fn TcState::pop1(st : TcState) -> Result[(TcState, ValType), String] {
  match st.stack.pop() {
    None =>
      if st.polymorphic {
        // In unreachable code, missing values below the current frame are
        // polymorphic, but concrete values pushed afterward must still be
        // consumed from the real stack first.
        Ok((st, ValType::bottom()))
      } else {
        Err("stack underflow")
      }
    Some(v) => Ok((st, v))
  }
}

///|
fn TcState::pop_expect(
  st : TcState,
  expected : ValType,
  operand_pairs~ : Array[Int32]? = None,
) -> TcResult {
  ignore(operand_pairs)
  match st.pop_expect_error(expected) {
    Some(error) => Err(error)
    None => Ok(st)
  }
}

///|
// Success always retains this state and mutates only its owned operand stack.
// Internal consumers can keep the state without allocating Ok per operand.
fn TcState::pop_expect_error(st : TcState, expected : ValType) -> String? {
  // The stack is already owned and mutated by pop1. Check its popped value
  // directly so typed operands do not allocate an intermediate state/value pair.
  let actual = match st.stack.pop() {
    Some(value) => value
    None =>
      if st.polymorphic {
        return None
      } else {
        return Some("stack underflow")
      }
  }
  if actual == ValType::bottom() || Match::matches(actual, expected, st.env) {
    None
  } else {
    Some("type mismatch: expected=\{expected} actual=\{actual}")
  }
}

///|
fn typecheck_drop(st : TcState) -> TcResult {
  match st.pop1() {
    Err(e) => Err(e)
    Ok((st2, _)) => Ok(st2)
  }
}

///|
fn TcState::push_types(st : TcState, ts : Array[ValType]) -> TcState {
  for t in ts {
    st.stack.push(t)
  }
  st
}

///|
fn TcState::pop_types(st : TcState, ts : Array[ValType]) -> TcResult {
  for i = ts.length() - 1; i >= 0; i = i - 1 {
    if st.pop_expect_error(ts[i]) is Some(error) {
      return Err(error)
    }
  }
  Ok(st)
}

///|
// Direct callers retain this state and need only an operand error. Keep the
// public Result-returning pop_types contract unchanged.
fn TcState::pop_types_error(st : TcState, ts : Array[ValType]) -> String? {
  for i = ts.length() - 1; i >= 0; i = i - 1 {
    if st.pop_expect_error(ts[i]) is Some(error) {
      return Some(error)
    }
  }
  None
}

///|
fn check_pop_types_from_top(
  st : TcState,
  ts : Array[ValType],
  count : Int32,
) -> Result[Int32, String] {
  let mut stack_i = match latest_stack_index(st.stack.length()) {
    Some(idx) => idx
    None => -1
  }
  let mut i = count - 1
  while i >= 0 {
    let expected = ts[i]
    if stack_i < 0 {
      if !st.polymorphic {
        return Err("stack underflow")
      }
    } else {
      let actual = st.stack[stack_i]
      if actual != ValType::bottom() &&
        !Match::matches(actual, expected, st.env) {
        return Err("type mismatch")
      }
      stack_i -= 1
    }
    i -= 1
  }
  Ok(stack_i)
}

///|
fn TcState::check_pop_types(
  st : TcState,
  ts : Array[ValType],
) -> Result[Unit, String] {
  match check_pop_types_from_top(st, ts, ts.length()) {
    Ok(_) => Ok(())
    Err(e) => Err(e)
  }
}

///|
fn TcState::retype_label_prefix(
  st : TcState,
  ts : Array[ValType],
  prefix_len : Int32,
) -> TcResult {
  for i = prefix_len - 1; i >= 0; i = i - 1 {
    if st.pop_expect_error(ts[i]) is Some(error) {
      return Err(error)
    }
  }
  // Conditional branch fallthrough uses the declared label types. This also
  // materializes operands supplied by polymorphic underflow in a dead frame.
  for i in 0.. Array[ValType] {
  ts.copy()
}

///|
fn validate_end_stack(
  st : TcState,
  results : Array[ValType],
) -> Result[Unit, String] {
  let top_after_results = match
    check_pop_types_from_top(st, results, results.length()) {
    Err(e) => return Err(e)
    Ok(i) => i
  }
  if top_after_results != -1 {
    return Err("type mismatch")
  }
  Ok(())
}

///|
fn normalize_untyped_if_branch_exit(
  st : TcState,
  results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
  let exit = normalize_untyped_if_branch_exit_value(st, results)
  match exit.error {
    Some(error) => Err(error)
    None => Ok((exit.reachable, exit.escape))
  }
}

///|
#valtype
priv struct TcIfBranchExit {
  reachable : Bool
  escape : TcEscape
  error : String?
}

///|
fn TcIfBranchExit::new(
  reachable : Bool,
  escape : TcEscape,
  error~ : String? = None,
) -> TcIfBranchExit {
  { reachable, escape, error, }
}

///|
fn normalize_untyped_if_branch_exit_value(
  st : TcState,
  results : Array[ValType],
) -> TcIfBranchExit {
  match validate_end_stack(st, results) {
    Err(e) => return TcIfBranchExit::new(false, NoTcEscape, error=Some(e))
    Ok(_) => ()
  }
  if st.reachable {
    return TcIfBranchExit::new(true, NoTcEscape)
  }
  match st.escape {
    NoTcEscape | TerminalTcEscape =>
      TcIfBranchExit::new(false, TerminalTcEscape)
    BranchTcEscape(0) => TcIfBranchExit::new(true, NoTcEscape)
    BranchTcEscape(depth) =>
      TcIfBranchExit::new(false, BranchTcEscape(depth - 1))
  }
}

///|
fn merge_nonfallthrough_if_exits(left : TcEscape, right : TcEscape) -> TcEscape {
  if left == right {
    return left
  }
  TerminalTcEscape
}

///|
fn TcState::mem_at_of(st : TcState, mem : MemIdx) -> Result[ValType, String] {
  match st.env.get_memtype(mem) {
    None => Err("invalid memory index")
    Some(MemType(lim, _)) => Ok(lim.addr_valtype())
  }
}

///|
fn pop_types_rev(st : TcState, ts : Array[ValType]) -> TcResult {
  for i = ts.length() - 1; i >= 0; i = i - 1 {
    if st.pop_expect_error(ts[i]) is Some(error) {
      return Err(error)
    }
  }
  Ok(st)
}

///|
fn normalize_untyped_block_exit(
  st : TcState,
  results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
  match validate_end_stack(st, results) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  if st.reachable {
    return Ok((true, NoTcEscape))
  }
  match st.escape {
    NoTcEscape | TerminalTcEscape => Ok((false, TerminalTcEscape))
    BranchTcEscape(0) => Ok((true, NoTcEscape))
    BranchTcEscape(depth) => Ok((false, BranchTcEscape(depth - 1)))
  }
}

///|
fn normalize_loop_exit(
  st : TcState,
  results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
  match validate_end_stack(st, results) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  if st.reachable {
    return Ok((true, NoTcEscape))
  }
  match st.escape {
    BranchTcEscape(depth) =>
      if depth > 0 {
        Ok((false, BranchTcEscape(depth - 1)))
      } else {
        Ok((false, TerminalTcEscape))
      }
    _ => Ok((false, TerminalTcEscape))
  }
}

///|
pub impl Typecheck for Expr with fn typecheck(self, st0) {
  typecheck_expr_with_context(self, st0, true)
}

///|
/// Check a speculative expression with the same checks and state ownership as
/// `Typecheck::typecheck`, omitting only the outer instruction/stack error
/// wrapper. Nested diagnostics and the first underlying failure are retained.
pub fn typecheck_expr_for_probe(expr : Expr, state : TcState) -> TcResult {
  typecheck_expr_with_context(expr, state, false)
}

///|
fn typecheck_expr_with_context(
  expr : Expr,
  st0 : TcState,
  include_context : Bool,
) -> TcResult {
  tc_iterative_expr(expr, st0, include_context)
}

///|
fn typecheck_select_typed(st : TcState, ts : Array[ValType]) -> TcResult {
  if Validate::validate(ts, st.env) is Err(t) {
    return Err("invalid select type annotation: " + t)
  }

  // condition
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match pop_types_rev(st1, ts) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match pop_types_rev(st2, ts) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // push ts
  let mut out = st3
  for t in ts {
    out = out.push1(t)
  }
  Ok(out)
}

///|
fn typecheck_select_untyped(st : TcState) -> TcResult {
  // pop condition (i32)
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop second operand
  let (st2, t2) = match st1.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }

  // pop first operand
  let (st3, t1) = match st2.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }

  if t1 == ValType::bottom() {
    return Ok(st3.push1(t2))
  }
  if t2 == ValType::bottom() {
    return Ok(st3.push1(t1))
  }
  let same = Match::matches(t1, t2, st.env) && Match::matches(t2, t1, st.env)
  if !same {
    return Err("select requires both operands to have the same type")
  }
  Ok(st3.push1(t1))
}

///|
fn typecheck_local_get(st : TcState, idx : LocalIdx) -> TcResult {
  match typecheck_local_get_error(st, idx) {
    None => Ok(st)
    Some(error) => Err(error)
  }
}

///|
fn typecheck_local_get_error(st : TcState, idx : LocalIdx) -> String? {
  match st.env.get_local_type(idx) {
    None => Some("invalid local index")
    Some(vt) => {
      let LocalIdx(raw) = idx
      let local_id = raw.reinterpret_as_int()
      if local_id < 0 || local_id >= st.initialized_locals.length() {
        return Some("invalid local index")
      }
      if !st.initialized_locals[local_id] {
        return Some("uninitialized local: \{local_id}")
      }
      st.stack.push(vt)
      None
    }
  }
}

///|
fn typecheck_local_set(
  st : TcState,
  idx : LocalIdx,
  initialization_owner~ : TcInitializationOwner? = None,
) -> TcResult {
  match st.env.get_local_type(idx) {
    None => Err("invalid local index")
    Some(vt) =>
      // A successful pop keeps this state and mutates only its owned stack.
      // Initialization may still return a new state with an isolated mask.
      match st.pop_expect_error(vt) {
        Some(e) => Err(e)
        None => Ok(st.mark_local_initialized(idx, initialization_owner~))
      }
  }
}

///|
fn typecheck_local_tee(
  st : TcState,
  idx : LocalIdx,
  initialization_owner~ : TcInitializationOwner? = None,
) -> TcResult {
  match st.env.get_local_type(idx) {
    None => Err("invalid local index")
    Some(vt) =>
      // tee pops vt then pushes vt
      match st.pop_expect_error(vt) {
        Some(e) => Err(e)
        None =>
          Ok(st.mark_local_initialized(idx, initialization_owner~).push1(vt))
      }
  }
}

///|
fn typecheck_global_get(st : TcState, idx : GlobalIdx) -> TcResult {
  match st.env.get_global_type(idx) {
    None => Err("invalid global index")
    Some(GlobalType(vt, _mut)) => Ok(st.push1(vt))
  }
}

///|
fn typecheck_global_set(st : TcState, idx : GlobalIdx) -> TcResult {
  match st.env.get_global_type(idx) {
    None => Err("invalid global index")
    Some(GlobalType(vt, is_mut)) => {
      if !is_mut {
        return Err("global.set on immutable global")
      }
      st.pop_expect(vt)
    }
  }
}

// table.get x : [at] -> [t]

///|
fn typecheck_table_get(st : TcState, idx : TableIdx) -> TcResult {
  let TableType(rt, limits) = match st.env.get_table_type(idx) {
    None => return Err("invalid table index")
    Some(tt) => tt
  }
  let st1 = match st.pop_expect(limits.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(ValType::ref_type(rt)))
}

// table.set x : [at, t] -> []

///|
fn typecheck_table_set(st : TcState, idx : TableIdx) -> TcResult {
  let TableType(rt, limits) = match st.env.get_table_type(idx) {
    None => return Err("invalid table index")
    Some(tt) => tt
  }

  // pop value (t)
  let st1 = match st.pop_expect(ValType::ref_type(rt)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop index (the table's address type)
  let st2 = match st1.pop_expect(limits.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2)
}

// table.size x : [] -> [at]

///|
fn typecheck_table_size(st : TcState, idx : TableIdx) -> TcResult {
  match st.env.get_table_type(idx) {
    None => Err("invalid table index")
    Some(TableType(_, limits)) => Ok(st.push1(limits.addr_valtype()))
  }
}

// table.grow x : [t, at] -> [at]

///|
fn typecheck_table_grow(st : TcState, idx : TableIdx) -> TcResult {
  let TableType(rt, limits) = match st.env.get_table_type(idx) {
    None => return Err("invalid table index")
    Some(tt) => tt
  }

  // pop delta (the table's address type) — top of stack
  let st1 = match st.pop_expect(limits.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop init value (t)
  let st2 = match st1.pop_expect(ValType::ref_type(rt)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(limits.addr_valtype()))
}

///|
fn typecheck_br(st : TcState, l : LabelIdx) -> TcResult {
  let ts = match st.env.get_label_types(l) {
    None => return Err("invalid label index")
    Some(x) => x
  }
  let st1 = match st.pop_types(ts) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let LabelIdx(depth) = l
  Ok(st1.set_branch_escape(depth.reinterpret_as_int()))
}

///|
fn typecheck_block(st : TcState, bt : BlockType, body : Expr) -> TcResult {
  let Expr(body_instrs) = body
  let (params, results) = match st.env.expand_blocktype(bt) {
    Err(e) => return Err(e)
    Ok(x) => x
  }

  // Consume params from the outer stack.
  let st1 = match st.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // Body instructions only see block params above their label boundary.
  let body_stack = tc_stack_from_types(params)
  let env2 = st1.env.with_label(results)
  let st_body0 = TcState::{
    env: env2,
    stack: body_stack,
    reachable: st1.reachable,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals: tc_fork_initialized_for_body(
      st1.initialized_locals,
      body_instrs,
    ),
  }
  let st_body = match Typecheck::typecheck(Expr::new(body_instrs), st_body0) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  tc_state_finish_block(st, st1, st_body, results)
}

///|
// Complete a prechecked control region without walking its instructions again.
pub fn tc_state_finish_block(
  outer : TcState,
  entry : TcState,
  body : TcState,
  results : Array[ValType],
) -> Result[TcState, String] {
  let (base_reaches_merge, block_escape) = match
    normalize_untyped_block_exit(body, results) {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  let reaches_merge = base_reaches_merge ||
    tc_escape_depths_has(body.reachable_escape_depths, 0)
  if reaches_merge {
    let out = TcState::{
      env: outer.env,
      stack: entry.stack,
      reachable: outer.reachable,
      polymorphic: outer.polymorphic,
      escape: outer.escape,
      reachable_escape_depths: outer.reachable_escape_depths,
      // Inner writes cannot initialize a local in the parent control frame.
      initialized_locals: entry.initialized_locals,
    }.merge_child_reachable_escape_depths(body)
    return Ok(out.push_types(results))
  }
  Ok(
    TcState::{
      env: outer.env,
      stack: entry.stack,
      reachable: false,
      polymorphic: outer.polymorphic,
      escape: if outer.reachable {
        block_escape
      } else {
        outer.escape
      },
      reachable_escape_depths: outer.reachable_escape_depths,
      initialized_locals: outer.initialized_locals,
    }
    .merge_child_reachable_escape_depths(body)
    .push_types(results),
  )
}

///|
fn typecheck_if(
  st : TcState,
  bt : BlockType,
  then_instrs : Array[Instruction],
  else_instrs_opt : Array[Instruction]?,
) -> TcResult {
  let (params, results) = match st.env.expand_blocktype(bt) {
    Err(e) => return Err(e)
    Ok(x) => x
  }

  // pop condition
  let st0 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop params
  let st1 = match st0.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let else_instrs = match else_instrs_opt {
    None => []
    Some(xs) => xs
  }

  // typecheck then branch
  let branch_stack = tc_stack_from_types(params)
  let env_branch = st1.env.with_label(results)
  let st_then0 = TcState::{
    env: env_branch,
    stack: branch_stack,
    reachable: st1.reachable,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals: tc_fork_initialized_for_body(
      st1.initialized_locals,
      then_instrs,
    ),
  }
  let st_then = match Typecheck::typecheck(Expr::new(then_instrs), st_then0) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // typecheck else branch (same starting state!)
  let st_else0 = TcState::{
    env: env_branch,
    stack: tc_stack_from_types(params),
    reachable: st1.reachable,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals: tc_fork_initialized_for_body(
      st1.initialized_locals,
      else_instrs,
    ),
  }
  let st_else = match Typecheck::typecheck(Expr::new(else_instrs), st_else0) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  tc_state_finish_if(st, st1, st_then, st_else, results)
}

///|
// Complete a prechecked control region without walking its instructions again.
pub fn tc_state_finish_if(
  st : TcState,
  st1 : TcState,
  st_then : TcState,
  st_else : TcState,
  results : Array[ValType],
) -> Result[TcState, String] {
  let then_exit = normalize_untyped_if_branch_exit_value(st_then, results)
  if then_exit.error is Some(error) {
    return Err(error)
  }
  let else_exit = normalize_untyped_if_branch_exit_value(st_else, results)
  if else_exit.error is Some(error) {
    return Err(error)
  }
  let then_reach = then_exit.reachable ||
    tc_escape_depths_has(st_then.reachable_escape_depths, 0)
  let else_reach = else_exit.reachable ||
    tc_escape_depths_has(st_else.reachable_escape_depths, 0)
  let merged_child_depths = tc_escape_depths_merge_children(
    st_then.reachable_escape_depths,
    st_else.reachable_escape_depths,
  )

  if then_reach || else_reach {
    let out = TcState::{
      env: st.env,
      stack: st1.stack,
      reachable: st.reachable,
      polymorphic: st.polymorphic,
      escape: st.escape,
      reachable_escape_depths: tc_escape_depths_merge(
        st.reachable_escape_depths,
        merged_child_depths,
      ),
      // Each arm's initialization scope ends with the if control frame.
      initialized_locals: st1.initialized_locals,
    }
    return Ok(out.push_types(results))
  }
  Ok(
    TcState::{
      env: st.env,
      stack: st1.stack,
      reachable: false,
      polymorphic: st.polymorphic,
      escape: if st.reachable {
        merge_nonfallthrough_if_exits(then_exit.escape, else_exit.escape)
      } else {
        st.escape
      },
      reachable_escape_depths: tc_escape_depths_merge(
        st.reachable_escape_depths,
        merged_child_depths,
      ),
      initialized_locals: st.initialized_locals,
    }.push_types(results),
  )
}

///|
fn typecheck_br_if(st : TcState, l : LabelIdx) -> TcResult {
  let ts = match st.env.get_label_types(l) {
    None => return Err("invalid label index")
    Some(x) => x
  }

  // Pop condition first
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  let st2 = match st1.pop_types(ts) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push_types(ts))
}

///|
fn typecheck_br_table(
  st : TcState,
  ls : Array[LabelIdx],
  ldef : LabelIdx,
) -> TcResult {
  // pop selector
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let ts = match st.env.get_label_types(ldef) {
    None => return Err("invalid default label index")
    Some(x) => x
  }

  // Every target consumes the same number of operands, but its types need not
  // be identical: one actual operand can be a subtype of several reference
  // types, and a polymorphic bottom operand matches distinct numeric types.
  // Check each target against the same unconsumed operand stack.
  for l in ls {
    let ts_l = match st.env.get_label_types(l) {
      None => return Err("invalid label index in br_table")
      Some(x) => x
    }
    if ts_l.length() != ts.length() {
      return Err("br_table target labels have different argument arities")
    }
    if st1.check_pop_types(ts_l) is Err(error) {
      return Err(error)
    }
  }

  // Pop the branch arguments (values that will be passed to the target label)
  let st2 = match st1.pop_types(ts) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // br_table always branches - code after is unreachable
  let mut all_same = true
  for l in ls {
    if l != ldef {
      all_same = false
    }
  }
  if all_same {
    let LabelIdx(depth) = ldef
    Ok(st2.set_branch_escape(depth.reinterpret_as_int()))
  } else {
    Ok(st2.set_unreachable())
  }
}

///|
fn typecheck_return(st : TcState) -> TcResult {
  let ts = match st.env.return_type {
    None => return Err("return used with no function return type in context")
    Some(t) => t
  }
  let st1 = match st.pop_types(ts) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.set_unreachable())
}

///|
fn typecheck_call(st : TcState, fidx : FuncIdx) -> TcResult {
  let FuncType(params, results) = match st.env.get_functype_by_funcidx(fidx) {
    None => return Err("invalid function index")
    Some(ft) => ft
  }
  if st.pop_types_error(params) is Some(error) {
    return Err(error)
  }
  Ok(st.push_types(results))
}

///|
fn typecheck_call_indirect(
  st : TcState,
  tidx : TypeIdx,
  tid : TableIdx,
) -> TcResult {
  // Resolve the function type from the type index
  let FuncType(params, results) = match st.env.resolve_functype(tidx) {
    None => return Err("call_indirect type index is not a function type")
    Some(ft) => ft
  }

  // Table must exist and have element type compatible with funcref
  let TableType(elem_rt, limits) = match st.env.get_table_type(tid) {
    None => return Err("invalid table index")
    Some(tt) => tt
  }

  // Require elem_rt <: funcref (spec requires the table contains functions)
  let fr = ValType::funcref()
  let elem_vt = ValType::ref_type(elem_rt)
  if !Match::matches(elem_vt, fr, st.env) {
    return Err("call_indirect table element type is not funcref-compatible")
  }

  // Pop the table's address-width index (i32 for table32, i64 for table64).
  let st1 = match st.pop_expect(limits.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // Pop params
  let st2 = match st1.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push_types(results))
}

///|
fn typecheck_call_ref(st : TcState, tidx : TypeIdx) -> TcResult {
  let FuncType(params, results) = match st.env.resolve_functype(tidx) {
    None => return Err("call_ref type index is not a function type")
    Some(ft) => ft
  }

  // Pop funcref of type (ref null tidx)
  // Your representation: HeapTypeRefType(nullable, HeapType(TypeIdx))
  let fref = ValType::ref_type(RefType::new(true, HeapType::new(tidx)))
  let st1 = match st.pop_expect(fref) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // Pop params
  let st2 = match st1.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push_types(results))
}

///|
fn require_return_results(
  st : TcState,
  results : Array[ValType],
) -> Result[Unit, String] {
  match st.env.return_type {
    None => Err("return_call used with no function return type in context")
    Some(rt) =>
      if Match::matches(results, rt, st.env) {
        Ok(())
      } else {
        Err("tail call result mismatch")
      }
  }
}

///|
fn typecheck_return_call(st : TcState, fidx : FuncIdx) -> TcResult {
  let FuncType(params, results) = match st.env.get_functype_by_funcidx(fidx) {
    None => return Err("invalid function index")
    Some(ft) => ft
  }
  match require_return_results(st, results) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let st1 = match st.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.set_unreachable())
}

///|
fn typecheck_return_call_indirect(
  st : TcState,
  tidx : TypeIdx,
  t : TableIdx,
) -> TcResult {
  let FuncType(params, results) = match st.env.resolve_functype(tidx) {
    None => return Err("return_call_indirect type index is not a function type")
    Some(ft) => ft
  }
  match require_return_results(st, results) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let TableType(elem_rt, limits) = match st.env.get_table_type(t) {
    None => return Err("invalid table index")
    Some(tt) => tt
  }

  // elem_rt <: funcref
  let fr = ValType::ref_type(RefType::abs(AbsHeapType::func()))
  let elem_vt = ValType::ref_type(elem_rt)
  if !Match::matches(elem_vt, fr, st.env) {
    return Err(
      "return_call_indirect table element type is not funcref-compatible",
    )
  }
  let st1 = match st.pop_expect(limits.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.set_unreachable())
}

///|
fn typecheck_return_call_ref(st : TcState, tidx : TypeIdx) -> TcResult {
  let FuncType(params, results) = match st.env.resolve_functype(tidx) {
    None => return Err("return_call_ref type index is not a function type")
    Some(ft) => ft
  }
  match require_return_results(st, results) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let fref = ValType::ref_type(RefType::new(true, HeapType::new(tidx)))
  let st1 = match st.pop_expect(fref) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.set_unreachable())
}

///|
fn expect_branch_ref(v : ValType) -> Result[(HeapType, RefType), String] {
  match v {
    RefTypeValType(rt) => Ok((rt.get_heap_type(), rt))
    _ => Err("expected a reference type")
  }
}

///|
fn typecheck_br_on_null(st : TcState, l : LabelIdx) -> TcResult {
  let ts = match st.env.get_label_types(l) {
    None => return Err("invalid label index")
    Some(x) => x
  }

  // Pop operand: a nullable ref refines to non-null on fallthrough; a
  // statically non-null ref is also valid and simply cannot take the null edge.
  let (st1, v) = match st.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }
  let refined = match v {
    BotValType => ValType::bottom()
    RefTypeValType(rt_in) => {
      let ht = rt_in.get_heap_type()
      if Validate::validate(ht, st.env) is Err(t) {
        return Err("Invalid heap type in br_on_null operand: " + t)
      }
      ValType::ref_type(RefType::new(false, ht, exact=rt_in.is_exact()))
    }
    _ => return Err("expected a reference type")
  }
  let st2 = match st1.pop_types(ts) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push_types(ts).push1(refined))
}

///|
fn typecheck_br_on_non_null(st : TcState, l : LabelIdx) -> TcResult {
  let label_ts = match st.env.get_label_types(l) {
    None => return Err("invalid label index")
    Some(x) => x
  }
  let (st1, v) = match st.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }
  let end = match latest_stack_index(label_ts.length()) {
    Some(end) => end
    None => return Err("br_on_non_null label type too short")
  }
  match v {
    BotValType =>
      match label_ts[end] {
        RefTypeValType(_) => ()
        _ =>
          return Err("br_on_non_null label must end with non-nullable ref type")
      }
    _ => {
      let (ht, rt_in) = match expect_branch_ref(v) {
        Err(e) => return Err(e)
        Ok(x) => x
      }
      if Validate::validate(ht, st.env) is Err(t) {
        return Err("Invalid heap type in br_on_non_null operand: " + t)
      }

      // The taken edge contributes the tested ref as non-null. The actual branch
      // payload must match the label's final expected type.
      let non_null_rt = RefType::new(false, ht, exact=rt_in.is_exact())
      let want_last = ValType::ref_type(non_null_rt)
      let last = label_ts[end]
      if !Match::matches(want_last, last, st.env) {
        return Err("br_on_non_null label must end with non-nullable ref type")
      }
    }
  }
  st1.retype_label_prefix(label_ts, end)
}

///|
fn typecheck_br_on_cast(
  st : TcState,
  l : LabelIdx,
  op : CastOp,
  ht1 : HeapType,
  ht2 : HeapType,
) -> TcResult {
  let CastOp(n1, n2, exact1, exact2) = op
  let rt1 = RefType::new(n1, ht1, exact=exact1)
  let rt2 = RefType::new(n2, ht2, exact=exact2)
  if Validate::validate(rt1, st.env) is Err(t) {
    return Err("Invalid BrOnCast: " + t)
  }
  if Validate::validate(rt2, st.env) is Err(t) {
    return Err("Invalid BrOnCast: " + t)
  }
  let label_ts = match st.check_branch_args_present(l) {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  let end = match latest_stack_index(label_ts.length()) {
    Some(end) => end
    None => return Err("br_on_cast label type too short")
  }
  let rt_label = match label_ts[end] {
    RefTypeValType(r) => r
    _ => return Err("br_on_cast label must end with a reftype")
  }

  // rt2 <: rt1 and rt2 <: rt_label
  if !Match::matches(rt2, rt1, st.env) {
    return Err("rt2 does not match rt1")
  }
  if !Match::matches(rt2, rt_label, st.env) {
    return Err("rt2 does not match label rt")
  }

  // pop rt1
  let st1 = match st.pop_expect(ValType::ref_type(rt1)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  // Check label args excluding rt_label (because branch keeps rt2 / rt_label)
  // Per spec, label is t* rt, so the remaining stack must have t*
  let st2 = match st1.retype_label_prefix(label_ts, end) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  // push (rt1 \ rt2)
  let rt_out = match diff(rt1, rt2) {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  Ok(st2.push1(ValType::ref_type(rt_out)))
}

///|
fn typecheck_br_on_cast_desc_eq(
  st : TcState,
  l : LabelIdx,
  source : RefType,
  target : RefType,
  fail : Bool,
) -> TcResult {
  if Validate::validate(source, st.env) is Err(error) {
    return Err("Invalid descriptor cast source: " + error)
  }
  if Validate::validate(target, st.env) is Err(error) {
    return Err("Invalid descriptor cast target: " + error)
  }
  // Descriptor equality supports casts between compatible GC heaps without
  // requiring the target's nullability or exactness to subtype the source.
  let nullable_source = RefType::new(
    true,
    source.get_heap_type(),
    exact=source.is_exact(),
  )
  let nullable_target = RefType::new(
    true,
    target.get_heap_type(),
    exact=target.is_exact(),
  )
  if !typecheck_ref_test_or_cast_types_compatible(
      nullable_target,
      nullable_source,
      st.env,
    ) {
    return Err("descriptor cast types are incompatible")
  }
  let descriptor_idx = match target.get_heap_type() {
    HeapType(type_idx) =>
      match st.env.resolve_subtype(type_idx) {
        Some(subtype) =>
          match subtype.descriptor_type() {
            Some(descriptor_idx) => descriptor_idx
            None => return Err("descriptor cast target has no descriptor type")
          }
        None => return Err("descriptor cast target type does not exist")
      }
    _ => return Err("descriptor cast target must be a defined type")
  }
  let descriptor_type = ValType::ref_type(
    RefType::new(true, HeapType::new(descriptor_idx), exact=target.is_exact()),
  )
  let st1 = match st.pop_expect(descriptor_type) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  let label_types = match st1.check_branch_args_present(l) {
    Ok(types) => types
    Err(error) => return Err(error)
  }
  let end = match latest_stack_index(label_types.length()) {
    Some(end) => end
    None => return Err("descriptor cast label type too short")
  }
  let label_ref = match label_types[end] {
    RefTypeValType(ref_type) => ref_type
    _ => return Err("descriptor cast label must end with a reftype")
  }
  let source_diff = match diff(source, target) {
    Ok(ref_type) => ref_type
    Err(error) => return Err(error)
  }
  let branch_ref = if fail { source_diff } else { target }
  if !Match::matches(branch_ref, label_ref, st.env) {
    return Err("descriptor cast result does not match label rt")
  }
  let st2 = match st1.pop_expect(ValType::ref_type(source)) {
    Ok(next) => next
    Err(error) => return Err(error)
  }
  let st3 = match st2.retype_label_prefix(label_types, end) {
    Ok(next) => next
    Err(error) => return Err(error)
  }
  let fallthrough_ref = if fail { target } else { source_diff }
  Ok(st3.push1(ValType::ref_type(fallthrough_ref)))
}

///|
fn typecheck_br_on_cast_fail(
  st : TcState,
  l : LabelIdx,
  op : CastOp,
  ht1 : HeapType,
  ht2 : HeapType,
) -> TcResult {
  let CastOp(n1, n2, exact1, exact2) = op
  let rt1 = RefType::new(n1, ht1, exact=exact1)
  let rt2 = RefType::new(n2, ht2, exact=exact2)
  if Validate::validate(rt1, st.env) is Err(t) {
    return Err("Invalid RefType on BrOnCastFail: " + t)
  }
  if Validate::validate(rt2, st.env) is Err(t) {
    return Err("Invalid RefType on BrOnCastFail: " + t)
  }
  let label_ts = match st.check_branch_args_present(l) {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  let end = match latest_stack_index(label_ts.length()) {
    Some(end) => end
    None => return Err("br_on_cast_fail label type too short")
  }
  let rt_label = match label_ts[end] {
    RefTypeValType(r) => r
    _ => return Err("br_on_cast_fail label must end with a reftype")
  }
  if !Match::matches(rt2, rt1, st.env) {
    return Err("rt2 does not match rt1")
  }
  let rt_diff = match diff(rt1, rt2) {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  if !Match::matches(rt_diff, rt_label, st.env) {
    return Err("rt1 \\ rt2 does not match label rt")
  }

  // pop rt1
  let st1 = match st.pop_expect(ValType::ref_type(rt1)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.retype_label_prefix(label_ts, end) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(ValType::ref_type(rt2)))
}

///|
fn typecheck_throw(st : TcState, tag : TagIdx) -> TcResult {
  // validate tag index
  if Validate::validate(tag, st.env) is Err(e) {
    return Err(e)
  }

  // get payload params
  let TagType(tidx) = st.env.tags[tag.0.reinterpret_as_int()]
  let params = match st.env.resolve_functype(tidx) {
    Some(FuncType(ps, results)) => {
      if !results.is_empty() {
        return Err("throw: exception tag must have no results")
      }
      ps
    }
    None => return Err("throw: invalid tag type")
  }

  // pop payload
  let st1 = match st.pop_types(params) {
    Ok(s) => s
    Err(e) => return Err(e)
  }

  // throw always makes execution unreachable
  Ok(st1.set_unreachable())
}

///|
fn typecheck_throw_ref(st : TcState) -> TcResult {
  let st1 = match st.pop_expect(ValType::ref_null_exn()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.set_unreachable())
}

///|
fn typecheck_rethrow(st : TcState, catch_depth : LabelIdx) -> TcResult {
  let LabelIdx(raw_depth) = catch_depth
  if st.env.legacy_rethrow_catch_ordinal(raw_depth) is None {
    return Err("invalid legacy rethrow catch depth")
  }
  Ok(st.set_unreachable())
}

///|
fn tag_payload_types(env : Env, tag : TagIdx) -> Array[ValType] {
  let idx = tag.0
  // Guard against overflow - if idx >= 2^31, it would become negative when cast to Int
  if idx > 0x7FFFFFFF {
    return []
  }
  match env.tags.get(idx.reinterpret_as_int()) {
    Some(TagType(tidx)) =>
      match env.resolve_functype(tidx) {
        Some(FuncType(params, _)) => params
        None => []
      }
    None => []
  }
}

///|
fn typecheck_catch_clause(st : TcState, c : Catch) -> TcResult {
  match c {
    Catch(tag, lbl) => {
      if Validate::validate(tag, st.env) is Err(e) {
        return Err(e)
      }
      if st.env.resolve_tag_functype(tag) is Some(FuncType(_, results)) &&
        !results.is_empty() {
        return Err("catch: exception tag must have no results")
      }
      // label must expect tag's payload types
      let label_types = match st.env.get_catch_label_types(lbl) {
        Some(label_types) => label_types
        None => return Err("Invalid catch label index")
      }
      let payload = tag_payload_types(st.env, tag)
      if !Match::matches(payload, label_types, st.env) {
        return Err("catch types (payload) do not match label types")
      }
      Ok(st)
    }
    CatchRef(tag, lbl) => {
      if Validate::validate(tag, st.env) is Err(e) {
        return Err(e)
      }
      // label must expect tag's payload types + exnref
      let label_types = match st.env.get_catch_label_types(lbl) {
        Some(label_types) => label_types
        None => return Err("Invalid catch label index")
      }
      if st.env.resolve_tag_functype(tag) is Some(FuncType(_, results)) &&
        !results.is_empty() {
        return Err("catch_ref: exception tag must have no results")
      }
      let payload = tag_payload_types(st.env, tag)
      let expected = payload.copy()
      expected.push(
        ValType::ref_type(
          RefType::new(false, HeapType::abs(AbsHeapType::exn())),
        ),
      )
      if !Match::matches(expected, label_types, st.env) {
        return Err(
          "catch_ref types (payload + exnref) do not match label types",
        )
      }
      Ok(st)
    }
    CatchAll(lbl) => {
      // label must expect empty
      let label_types = match st.env.get_catch_label_types(lbl) {
        Some(label_types) => label_types
        None => return Err("Invalid catch label index")
      }
      if label_types.length() != 0 {
        return Err("catch_all label must expect no values")
      }
      Ok(st)
    }
    CatchAllRef(lbl) => {
      // label must expect just exnref
      let label_types = match st.env.get_catch_label_types(lbl) {
        Some(label_types) => label_types
        None => return Err("Invalid catch label index")
      }
      let expected = [
        ValType::ref_type(
          RefType::new(false, HeapType::abs(AbsHeapType::exn())),
        ),
      ]
      if !Match::matches(expected, label_types, st.env) {
        return Err("catch_all_ref label must expect exnref")
      }
      Ok(st)
    }
  }
}

///|
fn typecheck_legacy_try(
  st : TcState,
  bt : BlockType,
  body : Expr,
  catches : Array[@lib.LegacyCatch],
  delegate : LabelIdx?,
) -> TcResult {
  if catches.length() > 0 && delegate is Some(_) {
    return Err("legacy try cannot mix catches and delegate")
  }
  let (params, results) = match st.env.expand_blocktype(bt) {
    Err(err) => return Err(err)
    Ok(types) => types
  }
  let st1 = match st.pop_types(params) {
    Err(err) => return Err(err)
    Ok(next) => next
  }
  let scoped_env = st1.env.with_label(results)
  let body_state = TcState::{
    env: scoped_env,
    stack: tc_stack_from_types(params),
    reachable: st1.reachable,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals: st1.initialized_locals.copy(),
  }
  let checked_body = match Typecheck::typecheck(body, body_state) {
    Err(err) => return Err(err)
    Ok(next) => next
  }
  match validate_end_stack(checked_body, results) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let mut any_reachable = checked_body.reachable
  for catch_ in catches {
    let initial_types = match catch_ {
      @lib.LegacyCatch(tag, _) => {
        if Validate::validate(tag, st.env) is Err(err) {
          return Err(err)
        }
        if st.env.resolve_tag_functype(tag) is Some(FuncType(_, results)) &&
          !results.is_empty() {
          return Err("legacy catch: exception tag must have no results")
        }
        tag_payload_types(st.env, tag)
      }
      @lib.LegacyCatchAll(_) => []
    }
    let catch_body = match catch_ {
      @lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
        catch_body
    }
    let catch_state = TcState::{
      env: scoped_env.with_legacy_catch_depth(scoped_env.legacy_catch_depth + 1),
      stack: tc_stack_from_types(initial_types),
      reachable: st1.reachable,
      polymorphic: false,
      escape: NoTcEscape,
      reachable_escape_depths: [],
      initialized_locals: st1.initialized_locals.copy(),
    }
    let checked_catch = match Typecheck::typecheck(catch_body, catch_state) {
      Err(err) => return Err(err)
      Ok(next) => next
    }
    match validate_end_stack(checked_catch, results) {
      Err(err) => return Err(err)
      Ok(_) => ()
    }
    if checked_catch.reachable {
      any_reachable = true
    }
  }
  match delegate {
    Some(label) =>
      match st.env.get_catch_label_types(label) {
        Some(_) => ()
        None => return Err("invalid legacy try delegate label")
      }
    None => ()
  }
  Ok(
    TcState::{
      env: st.env,
      stack: st1.stack,
      reachable: any_reachable,
      polymorphic: st.polymorphic,
      escape: if st.reachable {
        checked_body.escape
      } else {
        st.escape
      },
      reachable_escape_depths: st.reachable_escape_depths,
      initialized_locals: st1.initialized_locals,
    }
    .merge_child_reachable_escape_depths(checked_body)
    .push_types(results),
  )
}

///|
fn typecheck_try_table(
  st : TcState,
  bt : BlockType,
  catches : Array[Catch],
  body : Expr,
) -> TcResult {
  let (params, results) = match st.env.expand_blocktype(bt) { // assuming you made it a method
    Err(e) => return Err(e)
    Ok(x) => x
  }

  // consume params
  let st1 = match st.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // extend context with innermost label = results
  let env2 = st1.env.with_label(results)
  let st_body0 = TcState::{
    env: env2,
    stack: tc_stack_from_types(params),
    reachable: st1.reachable,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    // The body expression owns its first initialization write. Borrowing here
    // preserves the entry mask for catches and the parent control frame.
    initialized_locals: st1.initialized_locals,
  }

  // body must typecheck under extended env
  let st_body = match Typecheck::typecheck(body, st_body0) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // Catch branch targets are resolved in the enclosing label space, not the
  // try_table's temporary body label.
  let st_catch_ctx = st1
  for c in catches {
    match typecheck_catch_clause(st_catch_ctx, c) {
      Err(e) => return Err(e)
      Ok(_) => ()
    }
  }

  match validate_end_stack(st_body, results) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }

  tc_state_finish_try_table(st, st1, st_body, results, catches)
}

///|
pub fn tc_state_finish_try_table(
  outer : TcState,
  entry : TcState,
  body : TcState,
  results : Array[ValType],
  catches : Array[Catch],
) -> TcResult {
  let mut out = match tc_state_finish_block(outer, entry, body, results) {
    Ok(out) => out
    Err(err) => return Err(err)
  }
  // Handler labels belong to the enclosing label space. A throw can take
  // them before the body's normal exit, even when that exit is unreachable.
  for catch_ in catches {
    let label = match catch_ {
      Catch(_, label)
      | CatchRef(_, label)
      | CatchAll(label)
      | CatchAllRef(label) => label
    }
    out = out.record_reachable_escape_label(entry.reachable, label)
  }
  Ok(out)
}

///|
fn typecheck_table_copy(st : TcState, x : TableIdx, y : TableIdx) -> TcResult {
  let TableType(rt1, l1) = match st.env.get_table_type(x) {
    None => return Err("invalid dest table index")
    Some(tt) => tt
  }
  let TableType(rt2, l2) = match st.env.get_table_type(y) {
    None => return Err("invalid src table index")
    Some(tt) => tt
  }

  // require rt2 <: rt1
  if !Match::matches(rt2, rt1, st.env) {
    return Err("table.copy source elem type does not match dest elem type")
  }
  let at_len = min_addr(l1, l2)

  // Pop len, src, dst (LIFO), with types at_len, at2, at1
  let st1 = match st.pop_expect(at_len.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(l2.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(l1.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3)
}

///|
fn typecheck_table_init(st : TcState, y : ElemIdx, x : TableIdx) -> TcResult {
  let TableType(rt1, l) = match st.env.get_table_type(x) {
    None => return Err("invalid table index")
    Some(tt) => tt
  }
  let elem = match st.env.get_elem(y) {
    None => return Err("invalid elem segment index")
    Some(e) => e
  }
  let rt2 = elem.reftype()
  if !Match::matches(rt2, rt1, st.env) {
    return Err("table.init elem segment type does not match table element type")
  }

  // Pop: len:i32, src:i32, dst:at  (LIFO order)
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(l.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3)
}

///|
fn typecheck_table_fill(st : TcState, x : TableIdx) -> TcResult {
  let TableType(rt, lim) = match st.env.get_table_type(x) {
    None => return Err("invalid table index")
    Some(tt) => tt
  }

  // pop len : i32
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop val : rt
  let st2 = match st1.pop_expect(ValType::ref_type(rt)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop start : at (derived from limits)
  let st3 = match st2.pop_expect(lim.addr_valtype()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3)
}

///|
fn typecheck_elem_drop(st : TcState, y : ElemIdx) -> TcResult {
  match st.env.get_elem(y) {
    None => Err("invalid elem segment index")
    Some(_) => Ok(st)
  }
}

// Check memory selection, alignment and offset before any operand-stack pop.

///|
#valtype
priv struct TcMemargCheck {
  error : String?
  address64 : Bool
}

///|
fn TcMemargCheck::success(address64 : Bool) -> TcMemargCheck {
  { error: None, address64, }
}

///|
fn TcMemargCheck::failure(error : String) -> TcMemargCheck {
  { error: Some(error), address64: false, }
}

///|
fn TcMemargCheck::address_type(self : TcMemargCheck) -> ValType {
  if self.address64 {
    ValType::i64()
  } else {
    ValType::i32()
  }
}

///|
fn memarg_check(
  st : TcState,
  ma : MemArg,
  default_mem : MemIdx,
  nbits : Int32,
) -> TcMemargCheck {
  let MemArg(U32(align_pow_u), mem_opt, U64(offset)) = ma
  let mem = match mem_opt {
    None => default_mem
    Some(m) => m
  }
  let MemType(lim, _) = match st.env.get_memtype(mem) {
    None => return TcMemargCheck::failure("invalid memory index in memarg")
    Some(mt) => mt
  }

  // (1) alignment: 2^n <= N/8
  let max_align_bytes = nbits / 8
  if max_align_bytes <= 0 {
    return TcMemargCheck::failure("invalid access width")
  }
  let align_pow = align_pow_u.reinterpret_as_int()
  if align_pow < 0 {
    return TcMemargCheck::failure("negative alignment power")
  }
  if align_pow >= 64 {
    return TcMemargCheck::failure("alignment too large")
  }
  let align_bytes : UInt64 = 1 << align_pow
  if align_bytes > max_align_bytes.to_uint64() {
    return TcMemargCheck::failure("memarg alignment too large for access width")
  }

  // (2) offset: m < 2^|at|
  let bits = lim.mem_addr_bits()
  if bits == 32 {
    if offset >= 1 << 32 {
      return TcMemargCheck::failure("memarg offset out of range for i32 memory")
    }
  } else {
    // i64 memory: offset is UInt64 so always < 2^64
    ()
  }
  TcMemargCheck::success(bits == 64)
}

///|
fn typecheck_load(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  result : ValType,
) -> TcResult {
  let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(result))
}

///|
fn memarg_check_atomic(
  st : TcState,
  ma : MemArg,
  default_mem : MemIdx,
  nbits : Int32,
) -> TcMemargCheck {
  let checked = memarg_check(st, ma, default_mem, nbits)
  if checked.error is Some(_) {
    return checked
  }
  // Preserve the existing second lookup. The immutable index selection is
  // repeated only here, keeping the common result to two value-type fields.
  let MemArg(_, mem_opt, _) = ma
  let mem = match mem_opt {
    None => default_mem
    Some(mem) => mem
  }
  match st.env.get_memtype(mem) {
    None => return TcMemargCheck::failure("invalid memory index in memarg")
    Some(_) => ()
  }
  checked
}

///|
fn typecheck_atomic_load(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  result : ValType,
) -> TcResult {
  let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(result))
}

///|
fn typecheck_store(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  value : ValType,
) -> TcResult {
  let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()

  // pop value first (top of stack)
  let st1 = match st.pop_expect(value) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop address
  let st2 = match st1.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2)
}

///|
fn typecheck_atomic_store(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  value : ValType,
) -> TcResult {
  let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(value) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2)
}

///|
fn typecheck_atomic_notify(st : TcState, ma : MemArg) -> TcResult {
  let checked = memarg_check_atomic(st, ma, MemIdx::new(0), 32)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(ValType::i32()))
}

///|
fn typecheck_atomic_wait(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  expected : ValType,
) -> TcResult {
  let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(ValType::i64()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(expected) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3.push1(ValType::i32()))
}

///|
fn typecheck_atomic_rmw(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  ty : ValType,
) -> TcResult {
  let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(ty) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(ty))
}

///|
fn typecheck_atomic_cmpxchg(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  ty : ValType,
) -> TcResult {
  let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(ty) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ty) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3.push1(ty))
}

///|
fn typecheck_loop(st : TcState, bt : BlockType, body : Expr) -> TcResult {
  let (params, results) = match st.env.expand_blocktype(bt) {
    Err(e) => return Err(e)
    Ok(x) => x
  }

  // consume params
  let st1 = match st.pop_types(params) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // loop label is params (not results)
  let env2 = st1.env.with_label(params)
  let st_body0 = TcState::{
    env: env2,
    stack: tc_stack_from_types(params),
    reachable: st1.reachable,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals: tc_fork_initialized_for_body(
      st1.initialized_locals,
      body.0,
    ),
  }
  let st_body = match Typecheck::typecheck(body, st_body0) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  tc_state_finish_loop(st, st1, st_body, results)
}

///|
// Complete a prechecked control region without walking its instructions again.
pub fn tc_state_finish_loop(
  st : TcState,
  st1 : TcState,
  st_body : TcState,
  results : Array[ValType],
) -> Result[TcState, String] {
  let (reaches_merge, loop_escape) = match
    normalize_loop_exit(st_body, results) {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  if reaches_merge {
    let out = TcState::{
      env: st.env,
      stack: st1.stack,
      reachable: st.reachable,
      polymorphic: st.polymorphic,
      escape: st.escape,
      reachable_escape_depths: st.reachable_escape_depths,
      initialized_locals: st1.initialized_locals,
    }.merge_child_reachable_escape_depths(st_body)
    return Ok(out.push_types(results))
  }
  Ok(
    TcState::{
      env: st.env,
      stack: st1.stack,
      reachable: false,
      polymorphic: st.polymorphic,
      escape: if st.reachable {
        loop_escape
      } else {
        st.escape
      },
      reachable_escape_depths: st.reachable_escape_depths,
      initialized_locals: st.initialized_locals,
    }
    .merge_child_reachable_escape_depths(st_body)
    .push_types(results),
  )
}

///|
fn typecheck_ref_null(st : TcState, rt : RefType) -> TcResult {
  if Validate::validate(rt, st.env) is Err(t) {
    return Err(t)
  }
  Ok(st.push1(ValType::ref_null_type(rt)))
}

///|
fn typecheck_ref_is_null(st : TcState) -> TcResult {
  let (st1, v) = match st.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }
  match v {
    RefTypeValType(_) | BotValType => Ok(st1.push1(ValType::i32()))
    _ => Err("ref.is_null expects a reference type")
  }
}

///|
fn inferred_ref_func_heap_type(env : Env, f : FuncIdx) -> HeapType {
  match env.get_functypeidx_by_funcidx(f) {
    Some(tidx) => return HeapType::new(tidx)
    None => ()
  }
  let ft = match env.get_functype_by_funcidx(f) {
    Some(ft) => ft
    None => return HeapType::abs(AbsHeapType::func())
  }
  let mut inferred : TypeIdx? = None
  for i in 0..
        if FuncType::new(params, results) == ft {
          let next = TypeIdx::new(i.reinterpret_as_uint())
          match inferred {
            None => inferred = Some(next)
            Some(_) => return HeapType::abs(AbsHeapType::func())
          }
        }
      _ => ()
    }
  }
  match inferred {
    Some(tidx) => HeapType::new(tidx)
    None => HeapType::abs(AbsHeapType::func())
  }
}

///|
fn typecheck_ref_func(st : TcState, f : FuncIdx) -> TcResult {
  if !st.env.has_func(f) {
    return Err("invalid function index")
  }
  let heap_type = inferred_ref_func_heap_type(st.env, f)
  let exact = match heap_type {
    HeapType(_) | DefTypeHeapType(_) => st.env.infer_exact_references
    AbsHeapTypeHeapType(_) | SharedAbsHeapTypeHeapType(_) => false
  }
  Ok(st.push1(ValType::ref_type(RefType::new(false, heap_type, exact~))))
}

///|
fn typecheck_ref_as_non_null(st : TcState) -> TcResult {
  let (st1, v) = match st.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }
  match v {
    BotValType => Ok(st1.push1(ValType::bottom()))
    RefTypeValType(rt) =>
      match rt {
        HeapTypeRefType(true, exact, ht) =>
          Ok(st1.push1(ValType::ref_type(RefType::new(false, ht, exact~))))
        HeapTypeRefType(false, _, _) => Ok(st1.push1(ValType::ref_type(rt)))
        AbsHeapTypeRefType(abs) =>
          Ok(
            st1.push1(
              ValType::ref_type(RefType::new(false, HeapType::abs(abs))),
            ),
          )
      }
    _ => Err("ref.as_non_null expects a reference type")
  }
}

///|
fn typecheck_ref_eq(st : TcState) -> TcResult {
  let (st1, right) = match st.pop1() {
    Ok(p) => p
    Err(e) => return Err(e)
  }
  let (st2, left) = match st1.pop1() {
    Ok(p) => p
    Err(e) => return Err(e)
  }
  let unshared = ValType::eqref_null()
  let shared = ValType::ref_type(
    RefType::new(true, HeapType::shared_abs(AbsHeapType::eq())),
  )
  if (
      (left is BotValType || Match::matches(left, unshared, st.env)) &&
      (right is BotValType || Match::matches(right, unshared, st.env))
    ) ||
    (
      (left is BotValType || Match::matches(left, shared, st.env)) &&
      (right is BotValType || Match::matches(right, shared, st.env))
    ) {
    Ok(st2.push1(ValType::i32()))
  } else {
    Err("ref.eq requires equality references with matching shareability")
  }
}

///|
fn typecheck_ref_get_desc(st : TcState, type_idx : TypeIdx) -> TcResult {
  let (st1, rt_in_opt) = match st.pop_ref_or_bot() {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  match rt_in_opt {
    None => ()
    Some(rt_in) => ignore(rt_in)
  }
  let result_vt = match st.env.descriptor_result_type(type_idx, rt_in_opt) {
    Ok(vt) => vt
    Err(e) => return Err(e)
  }
  Ok(st1.push1(result_vt))
}

///|
fn typecheck_ref_test_or_cast_types_compatible(
  target : RefType,
  operand : RefType,
  env : Env,
) -> Bool {
  let target_domain = heaptype_validation_domain(target.get_heap_type(), env)
  target_domain >= 0 &&
  target_domain == heaptype_validation_domain(operand.get_heap_type(), env)
}

///|
fn typecheck_ref_test(
  st : TcState,
  nullable : Bool,
  exact : Bool,
  ht : HeapType,
) -> TcResult {
  let rt = RefType::new(nullable, ht, exact~)
  if Validate::validate(rt, st.env) is Err(t) {
    return Err("invalid ref.test target reftype: " + t)
  }
  let (st1, rt_in) = match st.pop_ref_or_bot() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }
  match rt_in {
    Some(rt_in) =>
      if !typecheck_ref_test_or_cast_types_compatible(rt, rt_in, st.env) {
        return Err("ref.test target does not match operand type")
      }
    None => ()
  }
  Ok(st1.push1(ValType::i32()))
}

///|
fn typecheck_ref_cast(
  st : TcState,
  nullable : Bool,
  exact : Bool,
  ht : HeapType,
) -> TcResult {
  let rt = RefType::new(nullable, ht, exact~)
  if Validate::validate(rt, st.env) is Err(t) {
    return Err("invalid ref.cast target reftype: " + t)
  }
  let (st1, rt_in_opt) = match st.pop_ref_or_bot() {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  match rt_in_opt {
    None => ()
    Some(rt_in) =>
      if !typecheck_ref_test_or_cast_types_compatible(rt, rt_in, st.env) {
        return Err("ref.cast target does not match operand type")
      }
  }
  Ok(st1.push1(ValType::ref_type(rt)))
}

///|
fn typecheck_ref_test_desc(
  st : TcState,
  nullable : Bool,
  ht : HeapType,
) -> TcResult {
  let rt = match st.env.resolve_descriptor_target_ref_type(nullable, ht) {
    Ok(t) => t
    Err(e) => return Err(e)
  }
  let (st1, v) = match st.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }
  let rt_in = match v {
    RefTypeValType(r) => r
    _ => return Err("ref.test_desc expects a reference operand")
  }
  if !descriptor_compatible(rt, rt_in, st.env) {
    return Err("ref.test_desc target does not match operand type")
  }
  Ok(st1.push1(ValType::i32()))
}

///|
fn typecheck_ref_cast_desc_eq_descriptor_matches(
  descriptor : RefType,
  target : RefType,
  env : Env,
) -> Bool {
  match env.resolve_heaptype_subtype(descriptor.get_heap_type()) {
    Some(st) =>
      match st.describes_type() {
        Some(described_idx) => {
          if descriptor.is_nullable() {
            return false
          }
          let described = RefType::new(
            target.is_nullable(),
            HeapType::new(described_idx),
            exact=target.is_exact(),
          )
          Match::matches(described, target, env)
        }
        None => false
      }
    None => false
  }
}

///|
fn typecheck_ref_cast_desc_eq_maybe_descriptor_operand(
  descriptor : RefType,
  env : Env,
) -> Bool {
  match env.resolve_heaptype_subtype(descriptor.get_heap_type()) {
    Some(st) =>
      match st.describes_type() {
        Some(_) => true
        None => false
      }
    None => false
  }
}

///|
fn typecheck_ref_cast_desc_eq(
  st : TcState,
  nullable : Bool,
  exact : Bool,
  ht : HeapType,
) -> TcResult {
  let rt = match st.env.resolve_descriptor_target_ref_type(nullable, ht) {
    Ok(t) =>
      if exact {
        RefType::new(t.is_nullable(), t.get_heap_type(), exact=true)
      } else {
        t
      }
    Err(e) => return Err(e)
  }
  match ht {
    HeapType(type_idx) =>
      match st.env.resolve_struct_descriptor_type(type_idx) {
        Ok(Some(_)) => ()
        Ok(None) =>
          return Err("ref.cast_desc_eq target requires descriptor metadata")
        Err(e) => return Err(e)
      }
    _ => return Err("ref.cast_desc_eq target requires descriptor metadata")
  }
  let (st1, first_opt) = match st.pop_ref_or_bot() {
    Err(e) => return Err(e)
    Ok(x) => x
  }
  match first_opt {
    None => Ok(st1.push1(ValType::ref_type(rt)))
    Some(first) =>
      if typecheck_ref_cast_desc_eq_descriptor_matches(first, rt, st.env) {
        let (st2, rt_in_opt) = match st1.pop_ref_or_bot() {
          Err(e) => return Err(e)
          Ok(x) => x
        }
        match rt_in_opt {
          None => ()
          Some(rt_in) =>
            if !typecheck_ref_test_or_cast_types_compatible(rt, rt_in, st.env) {
              return Err("ref.cast_desc_eq target does not match operand type")
            }
        }
        Ok(st2.push1(ValType::ref_type(rt)))
      } else {
        if typecheck_ref_cast_desc_eq_maybe_descriptor_operand(first, st.env) {
          return Err("ref.cast_desc_eq target does not match operand type")
        }
        // Compatibility with Starshine's older one-operand descriptor-cast
        // surface. Current custom-descriptor binaries put the descriptor
        // operand on top of the inspected reference; locally built legacy
        // fixtures still model only the inspected reference.
        if !typecheck_ref_test_or_cast_types_compatible(rt, first, st.env) {
          return Err("ref.cast_desc_eq target does not match operand type")
        }
        Ok(st1.push1(ValType::ref_type(rt)))
      }
  }
}

///|
fn typecheck_struct_new(st : TcState, x : TypeIdx) -> TcResult {
  match st.env.resolve_struct_descriptor_type(x) {
    Ok(Some(_)) =>
      return Err(
        "struct.new requires struct.new_desc for descriptor-bearing struct types",
      )
    Ok(None) => ()
    Err(e) => return Err(e)
  }
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }

  // Compute expected argument types: unpack(storage) for each field, in order.
  let args : Array[ValType] = []
  for f in fields {
    let FieldType(stor, _) = f
    args.push(stor.unpack())
  }
  let st1 = match st.pop_types(args) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // push (ref x)
  Ok(
    st1.push1(
      ValType::ref_type(
        RefType::new(
          false,
          HeapType::new(x),
          exact=st.env.infer_exact_references,
        ),
      ),
    ),
  )
}

///|
fn typecheck_struct_new_default(st : TcState, x : TypeIdx) -> TcResult {
  match st.env.resolve_struct_descriptor_type(x) {
    Ok(Some(_)) =>
      return Err(
        "struct.new_default requires struct.new_default_desc for descriptor-bearing struct types",
      )
    Ok(None) => ()
    Err(e) => return Err(e)
  }
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }
  for f in fields {
    let FieldType(stor, _) = f
    let vt = stor.unpack()
    if !has_default(vt) {
      return Err("struct.new_default: no default for a non-null field type")
    }
  }
  Ok(
    st.push1(
      ValType::ref_type(
        RefType::new(
          false,
          HeapType::new(x),
          exact=st.env.infer_exact_references,
        ),
      ),
    ),
  )
}

///|
fn typecheck_struct_new_desc(st : TcState, x : TypeIdx) -> TcResult {
  let descriptor_type = match st.env.resolve_struct_descriptor_type(x) {
    Ok(Some(t)) => t
    Ok(None) =>
      return Err("struct.new_desc requires a descriptor-bearing struct type")
    Err(e) => return Err(e)
  }
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }

  let args : Array[ValType] = []
  for f in fields {
    let FieldType(stor, _) = f
    args.push(stor.unpack())
  }
  args.push(
    ValType::ref_type(
      RefType::new(true, HeapType::new(descriptor_type), exact=true),
    ),
  )

  let st1 = match st.pop_types(args) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(
    st1.push1(
      ValType::ref_type(RefType::new(false, HeapType::new(x), exact=true)),
    ),
  )
}

///|
fn typecheck_struct_new_default_desc(st : TcState, x : TypeIdx) -> TcResult {
  let descriptor_type = match st.env.resolve_struct_descriptor_type(x) {
    Ok(Some(t)) => t
    Ok(None) =>
      return Err(
        "struct.new_default_desc requires a descriptor-bearing struct type",
      )
    Err(e) => return Err(e)
  }
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }
  for f in fields {
    let FieldType(stor, _) = f
    let vt = stor.unpack()
    if !has_default(vt) {
      return Err(
        "struct.new_default_desc: no default for a non-null field type",
      )
    }
  }
  let st1 = match
    st.pop_expect(
      ValType::ref_type(
        RefType::new(true, HeapType::new(descriptor_type), exact=true),
      ),
    ) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(
    st1.push1(
      ValType::ref_type(RefType::new(false, HeapType::new(x), exact=true)),
    ),
  )
}

///|
fn typecheck_struct_get(
  st : TcState,
  x : TypeIdx,
  i : U32,
  atomic~ : Bool = false,
) -> TcResult {
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }
  let FieldType(stor, _) = match get_struct_field(fields, i) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  if stor.is_packed() {
    return Err(
      "struct.get used on packed field; use struct.get_s or struct.get_u",
    )
  }
  let val_ty = if atomic {
    match aggregate_atomic_value_type(stor, st.env, references=true) {
      Ok(vt) => vt
      Err(error) => return Err(error)
    }
  } else {
    stor.unpack()
  }
  let st1 = match
    st.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(val_ty))
}

///|
fn typecheck_struct_get_s(st : TcState, x : TypeIdx, i : U32) -> TcResult {
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }
  let FieldType(stor, _) = match get_struct_field(fields, i) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  if !stor.is_packed() {
    return Err("struct.get_s requires packed field")
  }
  let st1 = match
    st.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(ValType::i32()))
}

///|
fn typecheck_struct_get_u(st : TcState, x : TypeIdx, i : U32) -> TcResult {
  // identical checks, only semantics differ at runtime
  typecheck_struct_get_s(st, x, i)
}

///|
fn typecheck_struct_atomic_get(
  st : TcState,
  _order : AtomicOrder,
  x : TypeIdx,
  i : U32,
) -> TcResult {
  typecheck_struct_get(st, x, i, atomic=true)
}

///|
fn typecheck_struct_atomic_get_s(
  st : TcState,
  _order : AtomicOrder,
  x : TypeIdx,
  i : U32,
) -> TcResult {
  typecheck_struct_get_s(st, x, i)
}

///|
fn typecheck_struct_atomic_get_u(
  st : TcState,
  _order : AtomicOrder,
  x : TypeIdx,
  i : U32,
) -> TcResult {
  typecheck_struct_get_u(st, x, i)
}

///|
fn aggregate_atomic_value_type(
  stor : StorageType,
  env : Env,
  references~ : Bool = false,
  equality~ : Bool = false,
) -> Result[ValType, String] {
  if stor.is_packed() {
    return Err("aggregate atomic RMW/cmpxchg requires non-packed field")
  }
  let vt = stor.unpack()
  match vt {
    NumTypeValType(I32NumType) | NumTypeValType(I64NumType) => Ok(vt)
    RefTypeValType(rt) if references => {
      let domain = heaptype_validation_domain(rt.get_heap_type(), env)
      if domain != 0 && domain != 8 {
        return Err(
          "aggregate atomic reference storage must be an anyref subtype",
        )
      }
      if equality {
        let eq = if domain == 8 {
          HeapType::shared_abs(AbsHeapType::eq())
        } else {
          HeapType::abs(AbsHeapType::eq())
        }
        if !Match::matches(rt.get_heap_type(), eq, env) {
          return Err("aggregate atomic cmpxchg requires an eqref subtype")
        }
      }
      Ok(vt)
    }
    _ =>
      Err("aggregate atomic storage requires i32, i64, or an allowed reference")
  }
}

///|
fn aggregate_atomic_store_type(
  stor : StorageType,
  env : Env,
) -> Result[ValType, String] {
  if stor.is_packed() {
    Ok(ValType::i32())
  } else {
    aggregate_atomic_value_type(stor, env, references=true)
  }
}

///|
fn aggregate_atomic_expected_type(vt : ValType, env : Env) -> ValType {
  match vt {
    RefTypeValType(rt) =>
      ValType::ref_type(
        RefType::new(
          true,
          if heaptype_validation_domain(rt.get_heap_type(), env) == 8 {
            HeapType::shared_abs(AbsHeapType::eq())
          } else {
            HeapType::abs(AbsHeapType::eq())
          },
        ),
      )
    _ => vt
  }
}

///|
fn typecheck_struct_atomic_rmw(
  st : TcState,
  op : @lib.AggregateAtomicRmwOp,
  x : TypeIdx,
  i : U32,
) -> TcResult {
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }
  let ft = match get_struct_field(fields, i) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("struct.atomic.rmw on immutable field")
  }
  let FieldType(stor, _) = ft
  let val_ty = match
    aggregate_atomic_value_type(
      stor,
      st.env,
      references=op == @lib.AggregateAtomicRmwOp::xchg(),
    ) {
    Err(e) => return Err(e)
    Ok(vt) => vt
  }
  let st1 = match st.pop_expect(val_ty) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match
    st1.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(val_ty))
}

///|
fn typecheck_struct_atomic_cmpxchg(
  st : TcState,
  x : TypeIdx,
  i : U32,
) -> TcResult {
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }
  let ft = match get_struct_field(fields, i) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("struct.atomic.rmw.cmpxchg on immutable field")
  }
  let FieldType(stor, _) = ft
  let val_ty = match
    aggregate_atomic_value_type(stor, st.env, references=true, equality=true) {
    Err(e) => return Err(e)
    Ok(vt) => vt
  }
  let st1 = match st.pop_expect(val_ty) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match
    st1.pop_expect(aggregate_atomic_expected_type(val_ty, st.env)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match
    st2.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3.push1(val_ty))
}

///|
fn typecheck_struct_set(
  st : TcState,
  x : TypeIdx,
  i : U32,
  atomic~ : Bool = false,
) -> TcResult {
  let fields = match st.env.resolve_struct_fields(x) {
    Err(e) => return Err(e)
    Ok(fs) => fs
  }
  let ft = match get_struct_field(fields, i) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("struct.set on immutable field")
  }
  let FieldType(stor, _) = ft
  let val_ty = if atomic {
    match aggregate_atomic_store_type(stor, st.env) {
      Ok(vt) => vt
      Err(error) => return Err(error)
    }
  } else {
    stor.unpack()
  }

  // pop value then ref
  let st1 = match st.pop_expect(val_ty) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match
    st1.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2)
}

///|
fn typecheck_unary(st : TcState, t_in : ValType, t_out : ValType) -> TcResult {
  if st.pop_expect_error(t_in) is Some(error) {
    return Err(error)
  }
  Ok(st.push1(t_out))
}

///|
fn typecheck_binary(st : TcState, t : ValType, out : ValType) -> TcResult {
  if st.pop_expect_error(t) is Some(error) {
    return Err(error)
  }
  if st.pop_expect_error(t) is Some(error) {
    return Err(error)
  }
  Ok(st.push1(out))
}

///|
fn typecheck_v128_ternary(st : TcState) -> TcResult {
  // v128 v128 v128 -> v128
  let st1 = match st.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3.push1(ValType::v128()))
}

///|
fn typecheck_compare(st : TcState, t : ValType) -> TcResult {
  typecheck_binary(st, t, ValType::i32())
}

///|
fn typecheck_v128_unary(st : TcState) -> TcResult {
  typecheck_unary(st, ValType::v128(), ValType::v128())
}

///|
fn typecheck_v128_binary(st : TcState) -> TcResult {
  typecheck_binary(st, ValType::v128(), ValType::v128())
}

///|
fn typecheck_v128_compare(st : TcState) -> TcResult {
  // v128 v128 -> v128 (SIMD comparisons produce v128 masks)
  typecheck_v128_binary(st)
}

///|
fn typecheck_v128_shift(st : TcState) -> TcResult {
  // v128 i32 -> v128 (shift count is i32)
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(ValType::v128()))
}

///|
fn typecheck_lane_index(
  lane : LaneIdx,
  lane_count : Byte,
) -> Result[Unit, String] {
  let LaneIdx(n) = lane
  if n < lane_count {
    Ok(())
  } else {
    Err("invalid lane index")
  }
}

///|
fn typecheck_lane_extract(
  st : TcState,
  out : ValType,
  lane : LaneIdx,
  lane_count : Byte,
) -> TcResult {
  // v128 -> out (lane index is an immediate, not on stack)
  match typecheck_lane_index(lane, lane_count) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let st1 = match st.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(out))
}

///|
fn typecheck_lane_replace(
  st : TcState,
  in_v : ValType,
  lane : LaneIdx,
  lane_count : Byte,
) -> TcResult {
  // v128, in_v -> v128
  match typecheck_lane_index(lane, lane_count) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let st1 = match st.pop_expect(in_v) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(ValType::v128()))
}

///|
fn typecheck_splat(st : TcState, in_v : ValType) -> TcResult {
  // in_v -> v128
  typecheck_unary(st, in_v, ValType::v128())
}

///|
fn typecheck_v128_const(st : TcState) -> TcResult {
  Ok(st.push1(ValType::v128()))
}

///|
fn typecheck_v128_mem_load(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
) -> TcResult {
  let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()
  let st1 = match st.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(ValType::v128()))
}

///|
fn typecheck_v128_mem_store(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
) -> TcResult {
  let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()

  // pop value first
  let st1 = match st.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop address
  let st2 = match st1.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2)
}

///|
fn typecheck_v128_mem_load_zero(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
) -> TcResult {
  typecheck_v128_mem_load(st, ma, nbits)
}

///|
fn typecheck_v128_load_lane(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  lane : LaneIdx,
  lane_count : Byte,
) -> TcResult {
  match typecheck_lane_index(lane, lane_count) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()

  // pop existing vector (top of stack)
  let st1 = match st.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop address
  let st2 = match st1.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(ValType::v128()))
}

///|
fn typecheck_v128_store_lane(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
  lane : LaneIdx,
  lane_count : Byte,
) -> TcResult {
  match typecheck_lane_index(lane, lane_count) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
  match checked.error {
    Some(error) => return Err(error)
    None => ()
  }
  let at = checked.address_type()

  // pop vector (top of stack)
  let st1 = match st.pop_expect(ValType::v128()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop address
  let st2 = match st1.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2)
}

///|
fn typecheck_v128_mem_load_splat(
  st : TcState,
  ma : MemArg,
  nbits : Int32,
) -> TcResult {
  typecheck_v128_mem_load(st, ma, nbits)
}

///|
fn typecheck_v128_mem_load_extend64(st : TcState, ma : MemArg) -> TcResult {
  typecheck_v128_mem_load(st, ma, 64)
}

///|
fn typecheck_memory_size(st : TcState, mem : MemIdx) -> TcResult {
  let at = match st.mem_at_of(mem) {
    Err(e) => return Err(e)
    Ok(t) => t
  }
  Ok(st.push1(at))
}

///|
fn typecheck_memory_grow(st : TcState, mem : MemIdx) -> TcResult {
  let at = match st.mem_at_of(mem) {
    Err(e) => return Err(e)
    Ok(t) => t
  }
  let st1 = match st.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(at))
}

///|
fn typecheck_memory_init(
  st : TcState,
  data : DataIdx,
  mem : MemIdx,
) -> TcResult {
  if !st.env.has_data(data) {
    return Err("invalid data segment index")
  }
  let at = match st.mem_at_of(mem) {
    Err(e) => return Err(e)
    Ok(t) => t
  }

  // pop len:i32, src:i32, dst:at
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3)
}

///|
fn typecheck_data_drop(st : TcState, data : DataIdx) -> TcResult {
  if !st.env.has_data(data) {
    return Err("invalid data segment index")
  }
  Ok(st)
}

///|
fn typecheck_memory_copy(st : TcState, dst : MemIdx, src : MemIdx) -> TcResult {
  let MemType(lim1, _) = match st.env.get_memtype(dst) {
    None => return Err("invalid dest memory index")
    Some(mt) => mt
  }
  let MemType(lim2, _) = match st.env.get_memtype(src) {
    None => return Err("invalid src memory index")
    Some(mt) => mt
  }
  let at1 = lim1.addr_valtype()
  let at2 = lim2.addr_valtype()
  let at_len = min_addr_valtype(lim1, lim2)

  // pop len, src, dst
  let st1 = match st.pop_expect(at_len) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(at2) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(at1) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3)
}

///|
fn typecheck_memory_fill(st : TcState, mem : MemIdx) -> TcResult {
  let at = match st.mem_at_of(mem) {
    Err(e) => return Err(e)
    Ok(t) => t
  }

  // pop len:at, val:i32, dst:at
  let st1 = match st.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(at) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3)
}

///|
fn typecheck_ref_conversion(
  st : TcState,
  expected : ValType,
  result_heap : AbsHeapType,
) -> TcResult {
  let (st1, actual) = match st.pop1() {
    Err(e) => return Err(e)
    Ok(pair) => pair
  }
  match actual {
    BotValType => Ok(st1.push1(ValType::bottom()))
    RefTypeValType(ref_type) =>
      if Match::matches(actual, expected, st.env) {
        Ok(
          st1.push1(
            ValType::ref_type(
              RefType::new(ref_type.is_nullable(), HeapType::abs(result_heap)),
            ),
          ),
        )
      } else {
        Err("type mismatch")
      }
    _ => Err("type mismatch")
  }
}

///|
fn typecheck_any_convert_extern(st : TcState) -> TcResult {
  typecheck_ref_conversion(st, ValType::externref(), AbsHeapType::any())
}

///|
fn typecheck_extern_convert_any(st : TcState) -> TcResult {
  typecheck_ref_conversion(st, ValType::anyref(), AbsHeapType::extern_())
}

///|
fn typecheck_ref_i31(st : TcState) -> TcResult {
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st1.push1(ValType::i31ref()))
}

///|
fn typecheck_ref_i31_shared(st : TcState) -> TcResult {
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(
    st1.push1(
      ValType::ref_type(
        RefType::new(false, HeapType::shared_abs(AbsHeapType::i31())),
      ),
    ),
  )
}

///|
fn typecheck_i31_get(st : TcState) -> TcResult {
  let (next, actual) = match st.pop1() {
    Ok(p) => p
    Err(e) => return Err(e)
  }
  let shared = ValType::ref_type(
    RefType::new(true, HeapType::shared_abs(AbsHeapType::i31())),
  )
  if actual is BotValType ||
    Match::matches(actual, ValType::i31ref_nullable(), st.env) ||
    Match::matches(actual, shared, st.env) {
    Ok(next.push1(ValType::i32()))
  } else {
    Err("i31.get requires an i31 reference")
  }
}

///|
fn typecheck_array_new(st : TcState, x : TypeIdx) -> TcResult {
  let FieldType(sto, _) = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  let elem_vt = sto.unpack()

  // pop length : i32
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop init value : elem_vt
  let st2 = match st1.pop_expect(elem_vt) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(
    st2.push1(
      ValType::ref_type(
        RefType::new(
          false,
          HeapType::new(x),
          exact=st.env.infer_exact_references,
        ),
      ),
    ),
  )
}

///|
fn typecheck_array_new_default(st : TcState, x : TypeIdx) -> TcResult {
  let FieldType(sto, _) = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  let elem_vt = sto.unpack()
  if !has_default(elem_vt) {
    return Err("array.new_default: no default for element type")
  }
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(
    st1.push1(
      ValType::ref_type(
        RefType::new(
          false,
          HeapType::new(x),
          exact=st.env.infer_exact_references,
        ),
      ),
    ),
  )
}

///|
fn typecheck_array_new_fixed(st : TcState, x : TypeIdx, n : U32) -> TcResult {
  let FieldType(sto, _) = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  let elem_vt = sto.unpack()
  let U32(nu) = n
  let count = nu.reinterpret_as_int()
  if count < 0 {
    return Err("array.new_fixed: negative length")
  }
  let mut cur = st
  // pop count times
  for _ in 0.. return Err(e)
      Ok(s) => s
    }
  }
  Ok(
    cur.push1(
      ValType::ref_type(
        RefType::new(
          false,
          HeapType::new(x),
          exact=st.env.infer_exact_references,
        ),
      ),
    ),
  )
}

///|
fn typecheck_array_get(
  st : TcState,
  x : TypeIdx,
  atomic~ : Bool = false,
) -> TcResult {
  let FieldType(sto, _) = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  if sto.is_packed() {
    return Err(
      "array.get used on packed element; use array.get_s or array.get_u",
    )
  }
  let elem_ty = if atomic {
    match aggregate_atomic_value_type(sto, st.env, references=true) {
      Ok(vt) => vt
      Err(error) => return Err(error)
    }
  } else {
    sto.unpack()
  }
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::ref_array_nullable(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(elem_ty))
}

///|
fn typecheck_array_get_s(st : TcState, x : TypeIdx) -> TcResult {
  let FieldType(sto, _) = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  if !sto.is_packed() {
    return Err("array.get_s requires packed element type")
  }
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::ref_array_nullable(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st2.push1(ValType::i32()))
}

///|
fn typecheck_array_get_u(st : TcState, x : TypeIdx) -> TcResult {
  // same typing as get_s
  typecheck_array_get_s(st, x)
}

///|
fn typecheck_array_set(
  st : TcState,
  x : TypeIdx,
  atomic~ : Bool = false,
) -> TcResult {
  let ft = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("array.set on immutable array element type")
  }
  let FieldType(sto, _) = ft
  let elem_vt = if atomic {
    match aggregate_atomic_store_type(sto, st.env) {
      Ok(vt) => vt
      Err(error) => return Err(error)
    }
  } else {
    sto.unpack()
  }

  // pop value
  let st1 = match st.pop_expect(elem_vt) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop index
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop array ref
  let st3 = match st2.pop_expect(ValType::ref_array_nullable(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3)
}

///|
fn typecheck_array_atomic_rmw(
  st : TcState,
  op : @lib.AggregateAtomicRmwOp,
  x : TypeIdx,
) -> TcResult {
  let ft = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("array.atomic.rmw on immutable array element type")
  }
  let FieldType(sto, _) = ft
  let elem_vt = match
    aggregate_atomic_value_type(
      sto,
      st.env,
      references=op == @lib.AggregateAtomicRmwOp::xchg(),
    ) {
    Err(e) => return Err(e)
    Ok(vt) => vt
  }
  let st1 = match st.pop_expect(elem_vt) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(ValType::ref_array_nullable(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st3.push1(elem_vt))
}

///|
fn typecheck_array_atomic_cmpxchg(st : TcState, x : TypeIdx) -> TcResult {
  let ft = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("array.atomic.rmw.cmpxchg on immutable array element type")
  }
  let FieldType(sto, _) = ft
  let elem_vt = match
    aggregate_atomic_value_type(sto, st.env, references=true, equality=true) {
    Err(e) => return Err(e)
    Ok(vt) => vt
  }
  let st1 = match st.pop_expect(elem_vt) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match
    st1.pop_expect(aggregate_atomic_expected_type(elem_vt, st.env)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st4 = match st3.pop_expect(ValType::ref_array_nullable(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st4.push1(elem_vt))
}

///|
fn typecheck_array_len(st : TcState) -> TcResult {
  let (next, actual) = match st.pop1() {
    Ok(p) => p
    Err(e) => return Err(e)
  }
  let unshared = ValType::ref_type(
    RefType::new(true, HeapType::abs(AbsHeapType::array())),
  )
  let shared = ValType::ref_type(
    RefType::new(true, HeapType::shared_abs(AbsHeapType::array())),
  )
  if Match::matches(actual, unshared, st.env) ||
    Match::matches(actual, shared, st.env) {
    Ok(next.push1(ValType::i32()))
  } else {
    Err("array.len requires an array reference")
  }
}

///|
fn typecheck_array_fill(st : TcState, x : TypeIdx) -> TcResult {
  let ft = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("array.fill on immutable array element type")
  }
  let FieldType(sto, _) = ft
  let elem_vt = sto.unpack()

  // pop len
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop value
  let st2 = match st1.pop_expect(elem_vt) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop start
  let st3 = match st2.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop array ref
  let st4 = match st3.pop_expect(ValType::ref_null_array_of(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st4)
}

///|
fn typecheck_array_copy(st : TcState, x : TypeIdx, y : TypeIdx) -> TcResult {
  let ft_x = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  let ft_y = match st.env.resolve_array_field(y) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft_x.is_mutable() {
    return Err("array.copy requires destination array to be mutable")
  }
  let FieldType(sto_x, _) = ft_x
  let FieldType(sto_y, _) = ft_y
  let vx = sto_x.unpack()
  let vy = sto_y.unpack()
  if !Match::matches(vy, vx, st.env) {
    return Err(
      "array.copy source element type does not match destination element type",
    )
  }

  // pop len
  let st = match st.pop_expect(ValType::i32()) {
    Err(t) => return Err(t)
    Ok(t) => t
  }

  // pop src index
  let st = match st.pop_expect(ValType::i32()) {
    Err(t) => return Err(t)
    Ok(t) => t
  }

  // pop src array
  let st = match st.pop_expect(ValType::ref_null_array_of(y)) {
    Err(t) => return Err(t)
    Ok(t) => t
  }

  // pop dst index
  let st = match st.pop_expect(ValType::i32()) {
    Err(t) => return Err(t)
    Ok(t) => t
  }

  // pop dst array
  let st = match st.pop_expect(ValType::ref_null_array_of(x)) {
    Err(t) => return Err(t)
    Ok(t) => t
  }
  Ok(st)
}

///|
fn typecheck_array_init_data(
  st : TcState,
  x : TypeIdx,
  y : DataIdx,
) -> TcResult {
  if !st.env.has_data(y) {
    return Err("invalid data segment index")
  }
  let ft = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("array.init_data requires array element to be mutable")
  }
  let FieldType(sto, _) = ft
  let vt = sto.unpack()
  match vt {
    NumTypeValType(_) => ()
    VecTypeValType => ()
    _ =>
      return Err(
        "array.init_data requires unpack(element) to be numtype or vectype",
      )
  }

  // Stack: (ref null x) i32 i32 i32 -> ε
  // pop len, src, dst
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop array ref
  let st4 = match st3.pop_expect(ValType::ref_null_array_of(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st4)
}

///|
fn typecheck_array_init_elem(
  st : TcState,
  x : TypeIdx,
  y : ElemIdx,
) -> TcResult {
  let elem = match st.env.get_elem(y) {
    None => return Err("invalid elem segment index")
    Some(e) => e
  }
  let ft = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(f) => f
  }
  if !ft.is_mutable() {
    return Err("array.init_elem requires array element to be mutable")
  }
  let FieldType(sto, _) = ft

  // Spec: C.elems[y] ≤ zt (storage type), so storage type must be a ref valtype.
  let rt_array = match sto {
    ValTypeStorageType(RefTypeValType(rt)) => rt
    _ =>
      return Err(
        "array.init_elem requires array storage type to be a reference type",
      )
  }
  let rt_seg = elem.reftype()
  if !Match::matches(rt_seg, rt_array, st.env) {
    return Err(
      "array.init_elem elem segment type does not match array storage type",
    )
  }

  // Stack: (ref null x) i32 i32 i32 -> ε
  // pop len, src, dst
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st3 = match st2.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }

  // pop array ref
  let st4 = match st3.pop_expect(ValType::ref_null_array_of(x)) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(st4)
}

///|
fn typecheck_array_new_elem(st : TcState, x : TypeIdx, y : ElemIdx) -> TcResult {
  let elem = match st.env.get_elem(y) {
    None => return Err("invalid elem segment index")
    Some(e) => e
  }
  let FieldType(sto, _mut) = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  let rt_array = match sto {
    ValTypeStorageType(RefTypeValType(rt)) => rt
    _ =>
      return Err(
        "array.new_elem requires array element storage type to be a reftype",
      )
  }
  let rt_seg = elem.reftype()
  if !Match::matches(rt_seg, rt_array, st.env) {
    return Err(
      "array.new_elem elem segment type does not match array element reftype",
    )
  }

  // Stack: i32 i32 -> (ref x)
  // (the two i32 are typically src and len; order doesn't matter for typing as both are i32)
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(
    st2.push1(
      ValType::ref_type(
        RefType::new(
          false,
          HeapType::new(x),
          exact=st.env.infer_exact_references,
        ),
      ),
    ),
  )
}

///|
fn typecheck_array_new_data(st : TcState, x : TypeIdx, y : DataIdx) -> TcResult {
  if !st.env.has_data(y) {
    return Err("invalid data segment index")
  }
  let FieldType(sto, _mut) = match st.env.resolve_array_field(x) {
    Err(e) => return Err(e)
    Ok(ft) => ft
  }
  let vt = sto.unpack()
  match vt {
    NumTypeValType(_) => ()
    VecTypeValType => ()
    _ =>
      return Err(
        "array.new_data requires unpack(element) to be numtype or vectype",
      )
  }

  // Stack: i32 i32 -> (ref x)
  let st1 = match st.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Err(e) => return Err(e)
    Ok(s) => s
  }
  Ok(
    st2.push1(
      ValType::ref_type(
        RefType::new(
          false,
          HeapType::new(x),
          exact=st.env.infer_exact_references,
        ),
      ),
    ),
  )
}

///|
fn typecheck_string_array_ref(
  st : TcState,
  op_name : String,
  expected_storage : StorageType,
  require_mutable : Bool,
) -> TcResult {
  let want_array_ref = ValType::ref_type(
    RefType::new(true, HeapType::abs(AbsHeapType::array())),
  )
  let (st1, actual) = match st.pop1() {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if actual == ValType::bottom() {
    return Ok(st1)
  }
  if !Match::matches(actual, want_array_ref, st.env) {
    return Err(op_name + " requires a nullable array reference operand")
  }
  let rt = match actual {
    RefTypeValType(rt) => rt
    _ => return Err(op_name + " requires a nullable array reference operand")
  }
  let subtype = match st.env.resolve_heaptype_subtype(rt.get_heap_type()) {
    Some(subtype) => subtype
    None => return Err(op_name + " requires a concrete array type operand")
  }
  let field_type = match subtype.get_comptype() {
    ArrayCompType(field_type) => field_type
    _ => return Err(op_name + " requires an array type operand")
  }
  if require_mutable && !field_type.is_mutable() {
    return Err(op_name + " requires a mutable array element type")
  }
  if field_type.get_storage_type() != expected_storage {
    return Err(op_name + " requires a matching array element storage type")
  }
  Ok(st1)
}

///|
fn typecheck_string_new_array(
  st : TcState,
  expected_storage : StorageType,
) -> TcResult {
  let st1 = match st.pop_expect(ValType::i32()) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let st3 = match
    typecheck_string_array_ref(st2, "string.new_array", expected_storage, false) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok(st3.push1(typecheck_nonnull_string_type()))
}

///|
fn typecheck_string_encode_array(
  st : TcState,
  op_name : String,
  expected_storage : StorageType,
) -> TcResult {
  let st1 = match st.pop_expect(ValType::i32()) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let st2 = match
    typecheck_string_array_ref(st1, op_name, expected_storage, true) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let st3 = match st2.pop_expect(ValType::stringref()) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok(st3.push1(ValType::i32()))
}

///|
fn typecheck_nonnull_string_type() -> ValType {
  ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::string_())))
}

///|
fn typecheck_string_const(st : TcState) -> TcResult {
  Ok(st.push1(typecheck_nonnull_string_type()))
}

///|
fn typecheck_string_measure_wtf16(st : TcState) -> TcResult {
  match st.pop_expect(ValType::stringref()) {
    Ok(next) => Ok(next.push1(ValType::i32()))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_string_binary(st : TcState, result : ValType) -> TcResult {
  let st1 = match st.pop_expect(ValType::stringref()) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  match st1.pop_expect(ValType::stringref()) {
    Ok(next) => Ok(next.push1(result))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_stringview_wtf16_get_codeunit(st : TcState) -> TcResult {
  let st1 = match st.pop_expect(ValType::i32()) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  match st1.pop_expect(ValType::stringref()) {
    Ok(next) => Ok(next.push1(ValType::i32()))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_stringview_wtf16_slice(st : TcState) -> TcResult {
  let st1 = match st.pop_expect(ValType::i32()) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  match st2.pop_expect(ValType::stringref()) {
    Ok(next) => Ok(next.push1(typecheck_nonnull_string_type()))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_array_load_result(op : @lib.LoadOp) -> ValType? {
  match op {
    @lib.I32LoadOp
    | @lib.I32Load8SOp
    | @lib.I32Load8UOp
    | @lib.I32Load16SOp
    | @lib.I32Load16UOp => Some(ValType::i32())
    @lib.I64LoadOp
    | @lib.I64Load8SOp
    | @lib.I64Load8UOp
    | @lib.I64Load16SOp
    | @lib.I64Load16UOp
    | @lib.I64Load32SOp
    | @lib.I64Load32UOp => Some(ValType::i64())
    @lib.F32LoadOp => Some(ValType::f32())
    @lib.F64LoadOp => Some(ValType::f64())
    @lib.V128LoadOp => Some(ValType::v128())
    _ => None
  }
}

///|
fn typecheck_array_store_value(op : @lib.StoreOp) -> ValType? {
  match op {
    @lib.I32StoreOp | @lib.I32Store8Op | @lib.I32Store16Op =>
      Some(ValType::i32())
    @lib.I64StoreOp
    | @lib.I64Store8Op
    | @lib.I64Store16Op
    | @lib.I64Store32Op => Some(ValType::i64())
    @lib.F32StoreOp => Some(ValType::f32())
    @lib.F64StoreOp => Some(ValType::f64())
    @lib.V128StoreOp => Some(ValType::v128())
    _ => None
  }
}

///|
fn typecheck_backing_array_field(
  st : TcState,
  type_idx : TypeIdx,
  require_mutable : Bool,
) -> Result[Unit, String] {
  let field = match st.env.resolve_array_field(type_idx) {
    Ok(field) => field
    Err(err) => return Err(err)
  }
  let FieldType(storage, mutability) = field
  match storage {
    @lib.PackTypeStorageType(_)
    | @lib.ValTypeStorageType(@lib.NumTypeValType(_))
    | @lib.ValTypeStorageType(@lib.VecTypeValType) => ()
    _ => return Err("array memory operations require numeric or vector storage")
  }
  if require_mutable && mutability != Mut::var_() {
    return Err("backing-array stores require mutable array elements")
  }
  Ok(())
}

///|
fn typecheck_array_load_op(
  st : TcState,
  op : @lib.LoadOp,
  type_idx : TypeIdx,
  ma : MemArg,
) -> TcResult {
  if typecheck_array_memory_argument(ma, typecheck_array_load_alignment(op))
    is Err(err) {
    return Err(err)
  }
  let result = match typecheck_array_load_result(op) {
    Some(result) => result
    None => return Err("unsupported backing-array load opcode")
  }
  match typecheck_backing_array_field(st, type_idx, false) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let st1 = match st.pop_expect(ValType::i32()) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  match st1.pop_expect(ValType::ref_array_nullable(type_idx)) {
    Ok(next) => Ok(next.push1(result))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_array_store_op(
  st : TcState,
  op : @lib.StoreOp,
  type_idx : TypeIdx,
  ma : MemArg,
) -> TcResult {
  if typecheck_array_memory_argument(ma, typecheck_array_store_alignment(op))
    is Err(err) {
    return Err(err)
  }
  let value = match typecheck_array_store_value(op) {
    Some(value) => value
    None => return Err("unsupported backing-array store opcode")
  }
  match typecheck_backing_array_field(st, type_idx, true) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let st1 = match st.pop_expect(value) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  let st2 = match st1.pop_expect(ValType::i32()) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  st2.pop_expect(ValType::ref_array_nullable(type_idx))
}

///|
fn typecheck_cont_ref_type(
  type_idx : TypeIdx,
  nullable : Bool,
  exact : Bool,
) -> ValType {
  ValType::ref_type(RefType::new(nullable, HeapType::new(type_idx), exact~))
}

///|
fn typecheck_cont_func_type_idx(
  st : TcState,
  type_idx : TypeIdx,
) -> Result[TypeIdx, String] {
  match st.env.resolve_comptype(type_idx) {
    Some(ContCompType(func_type)) => Ok(func_type)
    _ => Err("expected a continuation type")
  }
}

///|
fn typecheck_cont_new(st : TcState, type_idx : TypeIdx) -> TcResult {
  let func_type = match typecheck_cont_func_type_idx(st, type_idx) {
    Ok(func_type) => func_type
    Err(err) => return Err(err)
  }
  let expected_func = ValType::ref_type(
    RefType::new(true, HeapType::new(func_type)),
  )
  match st.pop_expect(expected_func) {
    Ok(next) => Ok(next.push1(typecheck_cont_ref_type(type_idx, false, true)))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_cont_bind(
  st : TcState,
  source_type : TypeIdx,
  target_type : TypeIdx,
) -> TcResult {
  let FuncType(source_params, source_results) = match
    st.env.resolve_cont_functype(source_type) {
    Some(sig) => sig
    None => return Err("cont.bind source type is not a continuation type")
  }
  let FuncType(target_params, target_results) = match
    st.env.resolve_cont_functype(target_type) {
    Some(sig) => sig
    None => return Err("cont.bind target type is not a continuation type")
  }
  if source_params.length() < target_params.length() {
    return Err("cont.bind source has fewer parameters than target")
  }
  let bound_count = source_params.length() - target_params.length()
  for idx = 0; idx < target_params.length(); idx = idx + 1 {
    if !Match::matches(
        target_params[idx],
        source_params[bound_count + idx],
        st.env,
      ) {
      return Err("cont.bind remaining parameter type mismatch")
    }
  }
  if !Match::matches(source_results, target_results, st.env) {
    return Err("cont.bind result type mismatch")
  }
  let st1 = match
    st.pop_expect(typecheck_cont_ref_type(source_type, true, false)) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  let bound_types : Array[ValType] = []
  for idx = 0; idx < bound_count; idx = idx + 1 {
    bound_types.push(source_params[idx])
  }
  match st1.pop_types(bound_types) {
    Ok(next) =>
      Ok(next.push1(typecheck_cont_ref_type(target_type, false, true)))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_suspend(st : TcState, tag : TagIdx) -> TcResult {
  let FuncType(params, results) = match st.env.resolve_tag_functype(tag) {
    Some(sig) => sig
    None => return Err("suspend tag must exist and use a function type")
  }
  match st.pop_types(params) {
    Ok(next) => Ok(next.push_types(results))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_resume_handler_cont(
  st : TcState,
  handler_type : ValType,
  tag_results : Array[ValType],
  cont_results : Array[ValType],
) -> Result[Unit, String] {
  let handler_cont_type = match handler_type {
    RefTypeValType(HeapTypeRefType(_, _, HeapType(type_idx))) => type_idx
    _ =>
      return Err("resume handler label must receive a continuation reference")
  }
  let FuncType(handler_params, handler_results) = match
    st.env.resolve_cont_functype(handler_cont_type) {
    Some(sig) => sig
    None => return Err("resume handler label continuation type is invalid")
  }
  if !Match::matches(handler_params, tag_results, st.env) ||
    !Match::matches(tag_results, handler_params, st.env) {
    return Err("resume handler continuation parameter type mismatch")
  }
  if !Match::matches(handler_results, cont_results, st.env) ||
    !Match::matches(cont_results, handler_results, st.env) {
    return Err("resume handler continuation result type mismatch")
  }
  Ok(())
}

///|
fn typecheck_resume_handlers(
  st : TcState,
  cont_results : Array[ValType],
  handlers : Array[ResumeHandler],
) -> Result[Unit, String] {
  for handler in handlers {
    match handler {
      ResumeOnLabel(tag, label) => {
        let FuncType(tag_params, tag_results) = match
          st.env.resolve_tag_functype(tag) {
          Some(sig) => sig
          None => return Err("resume handler tag must exist")
        }
        let label_types = match st.env.get_label_types(label) {
          Some(types) => types
          None => return Err("resume handler label must exist")
        }
        if label_types.length() != tag_params.length() + 1 {
          return Err("resume handler label arity mismatch")
        }
        for idx = 0; idx < tag_params.length(); idx = idx + 1 {
          if !Match::matches(tag_params[idx], label_types[idx], st.env) {
            return Err("resume handler label tag payload mismatch")
          }
        }
        match
          typecheck_resume_handler_cont(
            st,
            label_types[label_types.length() - 1],
            tag_results,
            cont_results,
          ) {
          Ok(_) => ()
          Err(err) => return Err(err)
        }
      }
      ResumeOnSwitch(tag) => {
        let FuncType(tag_params, tag_results) = match
          st.env.resolve_tag_functype(tag) {
          Some(sig) => sig
          None => return Err("resume switch handler tag must exist")
        }
        if !tag_params.is_empty() {
          return Err("resume switch handler tag must have no parameters")
        }
        if tag_results != cont_results {
          return Err("resume switch handler tag result mismatch")
        }
      }
    }
  }
  Ok(())
}

///|
fn TcState::record_resume_edges(
  state : TcState,
  handlers : Array[ResumeHandler],
) -> TcState {
  let mut next = state
  for handler in handlers {
    match handler {
      ResumeOnLabel(_, label) =>
        next = next.record_reachable_escape_label(state.reachable, label)
      ResumeOnSwitch(_) => ()
    }
  }
  next
}

///|
fn typecheck_resume(
  st : TcState,
  type_idx : TypeIdx,
  handlers : Array[ResumeHandler],
) -> TcResult {
  let FuncType(params, results) = match st.env.resolve_cont_functype(type_idx) {
    Some(sig) => sig
    None => return Err("resume type is not a continuation type")
  }
  match typecheck_resume_handlers(st, results, handlers) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let st1 = match
    st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  match st1.pop_types(params) {
    Ok(next) => Ok(next.push_types(results).record_resume_edges(handlers))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_resume_throw(
  st : TcState,
  type_idx : TypeIdx,
  tag : TagIdx,
  handlers : Array[ResumeHandler],
) -> TcResult {
  let FuncType(_, results) = match st.env.resolve_cont_functype(type_idx) {
    Some(sig) => sig
    None => return Err("resume_throw type is not a continuation type")
  }
  let FuncType(tag_params, tag_results) = match
    st.env.resolve_tag_functype(tag) {
    Some(sig) => sig
    None => return Err("resume_throw tag must exist")
  }
  if !tag_results.is_empty() {
    return Err("resume_throw tag must have no results")
  }
  match typecheck_resume_handlers(st, results, handlers) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let st1 = match
    st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  match st1.pop_types(tag_params) {
    Ok(next) => Ok(next.push_types(results).record_resume_edges(handlers))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_resume_throw_ref(
  st : TcState,
  type_idx : TypeIdx,
  handlers : Array[ResumeHandler],
) -> TcResult {
  let FuncType(_, results) = match st.env.resolve_cont_functype(type_idx) {
    Some(sig) => sig
    None => return Err("resume_throw_ref type is not a continuation type")
  }
  match typecheck_resume_handlers(st, results, handlers) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let st1 = match
    st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  match st1.pop_expect(ValType::ref_null_exn()) {
    Ok(next) => Ok(next.push_types(results).record_resume_edges(handlers))
    Err(err) => Err(err)
  }
}

///|
fn typecheck_stack_switch(
  st : TcState,
  type_idx : TypeIdx,
  tag : TagIdx,
) -> TcResult {
  let FuncType(source_params, source_results) = match
    st.env.resolve_cont_functype(type_idx) {
    Some(sig) => sig
    None => return Err("switch type is not a continuation type")
  }
  if source_params.is_empty() {
    return Err("switch continuation must have a next-continuation parameter")
  }
  let next_cont_type = match source_params[source_params.length() - 1] {
    RefTypeValType(HeapTypeRefType(_, _, HeapType(type_idx))) => type_idx
    _ =>
      return Err(
        "switch continuation last parameter must be a continuation reference",
      )
  }
  let FuncType(target_params, target_results) = match
    st.env.resolve_cont_functype(next_cont_type) {
    Some(sig) => sig
    None => return Err("switch next-continuation parameter type is invalid")
  }
  let FuncType(tag_params, tag_results) = match
    st.env.resolve_tag_functype(tag) {
    Some(sig) => sig
    None => return Err("switch tag must exist")
  }
  if !tag_params.is_empty() {
    return Err("switch tag must have no parameters")
  }
  if !Match::matches(source_results, tag_results, st.env) ||
    !Match::matches(tag_results, target_results, st.env) {
    return Err("switch tag or continuation result type mismatch")
  }
  let st1 = match
    st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
    Ok(next) => next
    Err(err) => return Err(err)
  }
  let operand_types : Array[ValType] = []
  for idx = 0; idx < source_params.length() - 1; idx = idx + 1 {
    operand_types.push(source_params[idx])
  }
  match st1.pop_types(operand_types) {
    Ok(next) => Ok(next.push_types(target_params))
    Err(err) => Err(err)
  }
}

///|
pub impl Typecheck for Instruction with fn typecheck(self, st) {
  match self {
    Unreachable => Ok(st.set_unreachable())
    Nop => Ok(st)
    I32Const(_) => Ok(st.push1(ValType::i32()))
    I64Const(_) => Ok(st.push1(ValType::i64()))
    F32Const(_) => Ok(st.push1(ValType::f32()))
    F64Const(_) => Ok(st.push1(ValType::f64()))
    I32Load(ma) => typecheck_load(st, ma, 32, ValType::i32())
    I64Load(ma) => typecheck_load(st, ma, 64, ValType::i64())
    F32Load(ma) => typecheck_load(st, ma, 32, ValType::f32())
    F64Load(ma) => typecheck_load(st, ma, 64, ValType::f64())
    I32Load8S(ma) => typecheck_load(st, ma, 8, ValType::i32())
    I32Load8U(ma) => typecheck_load(st, ma, 8, ValType::i32())
    I32Load16S(ma) => typecheck_load(st, ma, 16, ValType::i32())
    I32Load16U(ma) => typecheck_load(st, ma, 16, ValType::i32())
    I64Load8S(ma) => typecheck_load(st, ma, 8, ValType::i64())
    I64Load8U(ma) => typecheck_load(st, ma, 8, ValType::i64())
    I64Load16S(ma) => typecheck_load(st, ma, 16, ValType::i64())
    I64Load16U(ma) => typecheck_load(st, ma, 16, ValType::i64())
    I64Load32S(ma) => typecheck_load(st, ma, 32, ValType::i64())
    I64Load32U(ma) => typecheck_load(st, ma, 32, ValType::i64())
    I32AtomicLoad(_, ma) => typecheck_atomic_load(st, ma, 32, ValType::i32())
    I64AtomicLoad(_, ma) => typecheck_atomic_load(st, ma, 64, ValType::i64())
    I32AtomicLoad8U(_, ma) => typecheck_atomic_load(st, ma, 8, ValType::i32())
    I32AtomicLoad16U(_, ma) => typecheck_atomic_load(st, ma, 16, ValType::i32())
    I64AtomicLoad8U(_, ma) => typecheck_atomic_load(st, ma, 8, ValType::i64())
    I64AtomicLoad16U(_, ma) => typecheck_atomic_load(st, ma, 16, ValType::i64())
    I64AtomicLoad32U(_, ma) => typecheck_atomic_load(st, ma, 32, ValType::i64())
    I32Store(ma) => typecheck_store(st, ma, 32, ValType::i32())
    I64Store(ma) => typecheck_store(st, ma, 64, ValType::i64())
    F32Store(ma) => typecheck_store(st, ma, 32, ValType::f32())
    F64Store(ma) => typecheck_store(st, ma, 64, ValType::f64())
    I32Store8(ma) => typecheck_store(st, ma, 8, ValType::i32())
    I32Store16(ma) => typecheck_store(st, ma, 16, ValType::i32())
    I64Store8(ma) => typecheck_store(st, ma, 8, ValType::i64())
    I64Store16(ma) => typecheck_store(st, ma, 16, ValType::i64())
    I64Store32(ma) => typecheck_store(st, ma, 32, ValType::i64())
    I32AtomicStore(_, ma) => typecheck_atomic_store(st, ma, 32, ValType::i32())
    I64AtomicStore(_, ma) => typecheck_atomic_store(st, ma, 64, ValType::i64())
    I32AtomicStore8(_, ma) => typecheck_atomic_store(st, ma, 8, ValType::i32())
    I32AtomicStore16(_, ma) =>
      typecheck_atomic_store(st, ma, 16, ValType::i32())
    I64AtomicStore8(_, ma) => typecheck_atomic_store(st, ma, 8, ValType::i64())
    I64AtomicStore16(_, ma) =>
      typecheck_atomic_store(st, ma, 16, ValType::i64())
    I64AtomicStore32(_, ma) =>
      typecheck_atomic_store(st, ma, 32, ValType::i64())
    Drop => typecheck_drop(st)
    Select(None) => typecheck_select_untyped(st)
    Select(Some(ts)) => typecheck_select_typed(st, ts)
    LocalGet(i) => typecheck_local_get(st, i)
    LocalSet(i) => typecheck_local_set(st, i)
    LocalTee(i) => typecheck_local_tee(st, i)
    GlobalGet(i) => typecheck_global_get(st, i)
    GlobalSet(i) => typecheck_global_set(st, i)
    TableGet(i) => typecheck_table_get(st, i)
    TableSet(i) => typecheck_table_set(st, i)
    TableSize(i) => typecheck_table_size(st, i)
    TableGrow(i) => typecheck_table_grow(st, i)
    TableCopy(i0, i1) => typecheck_table_copy(st, i0, i1)
    TableInit(x, y) => typecheck_table_init(st, x, y)
    TableFill(x) => typecheck_table_fill(st, x)
    ElemDrop(y) => typecheck_elem_drop(st, y)
    MemorySize(m) => typecheck_memory_size(st, m)
    MemoryGrow(m) => typecheck_memory_grow(st, m)
    MemoryAtomicNotify(ma) => typecheck_atomic_notify(st, ma)
    MemoryAtomicWait32(ma) => typecheck_atomic_wait(st, ma, 32, ValType::i32())
    MemoryAtomicWait64(ma) => typecheck_atomic_wait(st, ma, 64, ValType::i64())
    AtomicFence(_) => Ok(st)
    MemoryInit(d, m) => typecheck_memory_init(st, d, m)
    DataDrop(d) => typecheck_data_drop(st, d)
    MemoryCopy(d, s) => typecheck_memory_copy(st, d, s)
    MemoryFill(m) => typecheck_memory_fill(st, m)
    AtomicRmw(_, op, ma) =>
      match op {
        I32AtomicRmwAddOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
        I64AtomicRmwAddOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
        I32AtomicRmw8AddUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
        I32AtomicRmw16AddUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
        I64AtomicRmw8AddUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
        I64AtomicRmw16AddUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
        I64AtomicRmw32AddUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
        I32AtomicRmwSubOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
        I64AtomicRmwSubOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
        I32AtomicRmw8SubUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
        I32AtomicRmw16SubUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
        I64AtomicRmw8SubUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
        I64AtomicRmw16SubUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
        I64AtomicRmw32SubUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
        I32AtomicRmwAndOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
        I64AtomicRmwAndOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
        I32AtomicRmw8AndUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
        I32AtomicRmw16AndUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
        I64AtomicRmw8AndUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
        I64AtomicRmw16AndUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
        I64AtomicRmw32AndUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
        I32AtomicRmwOrOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
        I64AtomicRmwOrOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
        I32AtomicRmw8OrUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
        I32AtomicRmw16OrUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
        I64AtomicRmw8OrUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
        I64AtomicRmw16OrUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
        I64AtomicRmw32OrUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
        I32AtomicRmwXorOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
        I64AtomicRmwXorOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
        I32AtomicRmw8XorUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
        I32AtomicRmw16XorUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
        I64AtomicRmw8XorUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
        I64AtomicRmw16XorUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
        I64AtomicRmw32XorUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
        I32AtomicRmwXchgOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
        I64AtomicRmwXchgOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
        I32AtomicRmw8XchgUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
        I32AtomicRmw16XchgUOp =>
          typecheck_atomic_rmw(st, ma, 16, ValType::i32())
        I64AtomicRmw8XchgUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
        I64AtomicRmw16XchgUOp =>
          typecheck_atomic_rmw(st, ma, 16, ValType::i64())
        I64AtomicRmw32XchgUOp =>
          typecheck_atomic_rmw(st, ma, 32, ValType::i64())
      }
    AtomicCmpxchg(_, op, ma) =>
      match op {
        I32AtomicRmwCmpxchgOp =>
          typecheck_atomic_cmpxchg(st, ma, 32, ValType::i32())
        I64AtomicRmwCmpxchgOp =>
          typecheck_atomic_cmpxchg(st, ma, 64, ValType::i64())
        I32AtomicRmw8CmpxchgUOp =>
          typecheck_atomic_cmpxchg(st, ma, 8, ValType::i32())
        I32AtomicRmw16CmpxchgUOp =>
          typecheck_atomic_cmpxchg(st, ma, 16, ValType::i32())
        I64AtomicRmw8CmpxchgUOp =>
          typecheck_atomic_cmpxchg(st, ma, 8, ValType::i64())
        I64AtomicRmw16CmpxchgUOp =>
          typecheck_atomic_cmpxchg(st, ma, 16, ValType::i64())
        I64AtomicRmw32CmpxchgUOp =>
          typecheck_atomic_cmpxchg(st, ma, 32, ValType::i64())
      }
    Br(l) => {
      // Preserve the reachable target depth even though this path becomes
      // unreachable. A sibling path may continue, and an enclosing block must
      // still account for this branch when validating its merge result.
      let was_reachable = st.reachable
      match typecheck_br(st, l) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    Block(bt, e) => typecheck_block(st, bt, e)
    If(bt, th, el) => typecheck_if(st, bt, th, el)
    BrIf(l) => {
      let was_reachable = st.reachable
      match typecheck_br_if(st, l) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    BrTable(ls, ldef) => {
      let was_reachable = st.reachable
      match typecheck_br_table(st, ls, ldef) {
        Ok(out) => {
          let mut out = out.record_reachable_escape_label(was_reachable, ldef)
          for l in ls {
            out = out.record_reachable_escape_label(was_reachable, l)
          }
          Ok(out)
        }
        Err(err) => Err(err)
      }
    }
    BrOnNull(l) => {
      let was_reachable = st.reachable
      match typecheck_br_on_null(st, l) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    BrOnNonNull(l) => {
      let was_reachable = st.reachable
      match typecheck_br_on_non_null(st, l) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    BrOnCast(l, op, ht1, ht2) => {
      let was_reachable = st.reachable
      match typecheck_br_on_cast(st, l, op, ht1, ht2) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    BrOnCastFail(l, op, ht1, ht2) => {
      let was_reachable = st.reachable
      match typecheck_br_on_cast_fail(st, l, op, ht1, ht2) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    BrOnCastDescEq(l, source, target) => {
      let was_reachable = st.reachable
      match typecheck_br_on_cast_desc_eq(st, l, source, target, false) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    BrOnCastDescEqFail(l, source, target) => {
      let was_reachable = st.reachable
      match typecheck_br_on_cast_desc_eq(st, l, source, target, true) {
        Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
        Err(err) => Err(err)
      }
    }
    Return => typecheck_return(st)
    Call(f) => typecheck_call(st, f)
    CallIndirect(tidx, t) => typecheck_call_indirect(st, tidx, t)
    CallRef(tidx) => typecheck_call_ref(st, tidx)
    ReturnCall(fidx) => typecheck_return_call(st, fidx)
    ReturnCallIndirect(tidx, t) => typecheck_return_call_indirect(st, tidx, t)
    ReturnCallRef(tidx) => typecheck_return_call_ref(st, tidx)
    WaitqueueNew =>
      Ok(
        st.push1(
          ValType::ref_type(
            RefType::new(false, HeapType::shared_abs(AbsHeapType::waitqueue())),
          ),
        ),
      )
    WaitqueueNotify => typecheck_waitqueue_notify(st)
    StructWait(type_idx, field) => typecheck_struct_wait(st, type_idx, field)
    Publish =>
      match st.pop1() {
        Ok((next, RefTypeValType(ref_type))) =>
          Ok(next.push1(ValType::ref_type(ref_type)))
        Ok((next, BotValType)) => Ok(next.push1(ValType::bottom()))
        Ok(_) => Err("publish expects a reference")
        Err(err) => Err(err)
      }
    ContNew(type_idx) => typecheck_cont_new(st, type_idx)
    ContBind(source_type, target_type) =>
      typecheck_cont_bind(st, source_type, target_type)
    Suspend(tag) => typecheck_suspend(st, tag)
    Resume(type_idx, handlers) => typecheck_resume(st, type_idx, handlers)
    ResumeThrow(type_idx, tag, handlers) =>
      typecheck_resume_throw(st, type_idx, tag, handlers)
    ResumeThrowRef(type_idx, handlers) =>
      typecheck_resume_throw_ref(st, type_idx, handlers)
    StackSwitch(type_idx, tag) => typecheck_stack_switch(st, type_idx, tag)
    Throw(t) => typecheck_throw(st, t)
    ThrowRef => typecheck_throw_ref(st)
    Rethrow(depth) => typecheck_rethrow(st, depth)
    Try(bt, body, catches, delegate) =>
      typecheck_legacy_try(st, bt, body, catches, delegate)
    TryTable(bt, cs, e) => typecheck_try_table(st, bt, cs, e)
    Loop(bt, e) => typecheck_loop(st, bt, e)
    RefNull(ht) => typecheck_ref_null(st, ht)
    RefIsNull => typecheck_ref_is_null(st)
    RefFunc(f) => typecheck_ref_func(st, f)
    RefAsNonNull => typecheck_ref_as_non_null(st)
    RefEq => typecheck_ref_eq(st)
    StringConst(_) => typecheck_string_const(st)
    StringMeasureWtf16 => typecheck_string_measure_wtf16(st)
    StringConcat => typecheck_string_binary(st, typecheck_nonnull_string_type())
    StringEq => typecheck_string_binary(st, ValType::i32())
    StringAsWtf16 =>
      match st.pop_expect(ValType::stringref()) {
        Ok(next) => Ok(next.push1(typecheck_nonnull_string_type()))
        Err(err) => Err(err)
      }
    StringViewWtf16GetCodeunit => typecheck_stringview_wtf16_get_codeunit(st)
    StringViewWtf16Slice => typecheck_stringview_wtf16_slice(st)
    StringNewUtf8Array =>
      typecheck_string_new_array(st, StorageType::pack_type(PackType::i8()))
    StringNewWtf16Array =>
      typecheck_string_new_array(st, StorageType::pack_type(PackType::i16()))
    StringEncodeUtf8Array =>
      typecheck_string_encode_array(
        st,
        "string.encode_utf8_array",
        StorageType::pack_type(PackType::i8()),
      )
    StringEncodeWtf16Array =>
      typecheck_string_encode_array(
        st,
        "string.encode_wtf16_array",
        StorageType::pack_type(PackType::i16()),
      )
    StringNewLossyUtf8Array =>
      typecheck_string_new_array(st, StorageType::pack_type(PackType::i8()))
    StringNewWtf8Array =>
      typecheck_string_new_array(st, StorageType::pack_type(PackType::i8()))
    StringEncodeLossyUtf8Array =>
      typecheck_string_encode_array(
        st,
        "string.encode_lossy_utf8_array",
        StorageType::pack_type(PackType::i8()),
      )
    StringEncodeWtf8Array =>
      typecheck_string_encode_array(
        st,
        "string.encode_wtf8_array",
        StorageType::pack_type(PackType::i8()),
      )
    RefGetDesc(type_idx) => typecheck_ref_get_desc(st, type_idx)
    RefTest(nullable, exact, ht) => typecheck_ref_test(st, nullable, exact, ht)
    RefCast(nullable, exact, ht) => typecheck_ref_cast(st, nullable, exact, ht)
    RefTestDesc(nullable, ht) => typecheck_ref_test_desc(st, nullable, ht)
    RefCastDescEq(nullable, exact, ht) =>
      typecheck_ref_cast_desc_eq(st, nullable, exact, ht)
    AnyConvertExtern => typecheck_any_convert_extern(st)
    ExternConvertAny => typecheck_extern_convert_any(st)
    RefI31 => typecheck_ref_i31(st)
    RefI31Shared => typecheck_ref_i31_shared(st)
    I31GetS => typecheck_i31_get(st)
    I31GetU => typecheck_i31_get(st)
    StructNew(x) => typecheck_struct_new(st, x)
    StructNewDesc(x) => typecheck_struct_new_desc(st, x)
    StructNewDefault(x) => typecheck_struct_new_default(st, x)
    StructNewDefaultDesc(x) => typecheck_struct_new_default_desc(st, x)
    StructGet(x, i) => typecheck_struct_get(st, x, i)
    StructGetS(x, i) => typecheck_struct_get_s(st, x, i)
    StructGetU(x, i) => typecheck_struct_get_u(st, x, i)
    StructAtomicGet(order, x, i) => typecheck_struct_atomic_get(st, order, x, i)
    StructAtomicGetS(order, x, i) =>
      typecheck_struct_atomic_get_s(st, order, x, i)
    StructAtomicGetU(order, x, i) =>
      typecheck_struct_atomic_get_u(st, order, x, i)
    StructAtomicRmw(_, op, x, i) => typecheck_struct_atomic_rmw(st, op, x, i)
    StructAtomicCmpxchg(_, x, i) => typecheck_struct_atomic_cmpxchg(st, x, i)
    StructSet(x, i) => typecheck_struct_set(st, x, i)
    StructAtomicSet(_, x, i) => typecheck_struct_set(st, x, i, atomic=true)
    ArrayNew(x) => typecheck_array_new(st, x)
    ArrayNewDefault(x) => typecheck_array_new_default(st, x)
    ArrayNewFixed(x, n) => typecheck_array_new_fixed(st, x, n)
    ArrayGet(x) => typecheck_array_get(st, x)
    ArrayGetS(x) => typecheck_array_get_s(st, x)
    ArrayGetU(x) => typecheck_array_get_u(st, x)
    ArrayAtomicGet(_, x) => typecheck_array_get(st, x, atomic=true)
    ArrayAtomicGetS(_, x) => typecheck_array_get_s(st, x)
    ArrayAtomicGetU(_, x) => typecheck_array_get_u(st, x)
    ArraySet(x) => typecheck_array_set(st, x)
    ArrayAtomicSet(_, x) => typecheck_array_set(st, x, atomic=true)
    ArrayAtomicRmw(_, op, x) => typecheck_array_atomic_rmw(st, op, x)
    ArrayAtomicCmpxchg(_, x) => typecheck_array_atomic_cmpxchg(st, x)
    ArrayLoad(op, x, ma) => typecheck_array_load_op(st, op, x, ma)
    ArrayStore(op, x, ma) => typecheck_array_store_op(st, op, x, ma)
    ArrayLen => typecheck_array_len(st)
    ArrayFill(x) => typecheck_array_fill(st, x)
    ArrayCopy(x, y) => typecheck_array_copy(st, x, y)
    ArrayInitData(x, y) => typecheck_array_init_data(st, x, y)
    ArrayInitElem(x, y) => typecheck_array_init_elem(st, x, y)
    ArrayNewElem(x, y) => typecheck_array_new_elem(st, x, y)
    ArrayNewData(x, y) => typecheck_array_new_data(st, x, y)

    // ===== i32 relational =====
    I32Eqz => typecheck_unary(st, ValType::i32(), ValType::i32())
    I32Eq => typecheck_compare(st, ValType::i32())
    I32Ne => typecheck_compare(st, ValType::i32())
    I32LtS => typecheck_compare(st, ValType::i32())
    I32LtU => typecheck_compare(st, ValType::i32())
    I32GtS => typecheck_compare(st, ValType::i32())
    I32GtU => typecheck_compare(st, ValType::i32())
    I32LeS => typecheck_compare(st, ValType::i32())
    I32LeU => typecheck_compare(st, ValType::i32())
    I32GeS => typecheck_compare(st, ValType::i32())
    I32GeU => typecheck_compare(st, ValType::i32())

    // ===== i64 relational =====
    I64Eqz => typecheck_unary(st, ValType::i64(), ValType::i32())
    I64Eq => typecheck_compare(st, ValType::i64())
    I64Ne => typecheck_compare(st, ValType::i64())
    I64LtS => typecheck_compare(st, ValType::i64())
    I64LtU => typecheck_compare(st, ValType::i64())
    I64GtS => typecheck_compare(st, ValType::i64())
    I64GtU => typecheck_compare(st, ValType::i64())
    I64LeS => typecheck_compare(st, ValType::i64())
    I64LeU => typecheck_compare(st, ValType::i64())
    I64GeS => typecheck_compare(st, ValType::i64())
    I64GeU => typecheck_compare(st, ValType::i64())

    // ===== f32 relational =====
    F32Eq => typecheck_compare(st, ValType::f32())
    F32Ne => typecheck_compare(st, ValType::f32())
    F32Lt => typecheck_compare(st, ValType::f32())
    F32Gt => typecheck_compare(st, ValType::f32())
    F32Le => typecheck_compare(st, ValType::f32())
    F32Ge => typecheck_compare(st, ValType::f32())

    // ===== f64 relational =====
    F64Eq => typecheck_compare(st, ValType::f64())
    F64Ne => typecheck_compare(st, ValType::f64())
    F64Lt => typecheck_compare(st, ValType::f64())
    F64Gt => typecheck_compare(st, ValType::f64())
    F64Le => typecheck_compare(st, ValType::f64())
    F64Ge => typecheck_compare(st, ValType::f64())

    // ===== i32 unary =====
    I32Clz => typecheck_unary(st, ValType::i32(), ValType::i32())
    I32Ctz => typecheck_unary(st, ValType::i32(), ValType::i32())
    I32Popcnt => typecheck_unary(st, ValType::i32(), ValType::i32())

    // ===== i64 unary =====
    I64Clz => typecheck_unary(st, ValType::i64(), ValType::i64())
    I64Ctz => typecheck_unary(st, ValType::i64(), ValType::i64())
    I64Popcnt => typecheck_unary(st, ValType::i64(), ValType::i64())

    // ===== i32 binary =====
    I32Add => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32Sub => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32Mul => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32DivS => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32DivU => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32RemS => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32RemU => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32And => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32Or => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32Xor => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32Shl => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32ShrS => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32ShrU => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32Rotl => typecheck_binary(st, ValType::i32(), ValType::i32())
    I32Rotr => typecheck_binary(st, ValType::i32(), ValType::i32())

    // ===== i64 binary =====
    I64Add => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64Sub => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64Mul => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64DivS => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64DivU => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64RemS => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64RemU => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64And => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64Or => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64Xor => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64Shl => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64ShrS => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64ShrU => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64Rotl => typecheck_binary(st, ValType::i64(), ValType::i64())
    I64Rotr => typecheck_binary(st, ValType::i64(), ValType::i64())

    // ===== f32 unary =====
    F32Abs => typecheck_unary(st, ValType::f32(), ValType::f32())
    F32Neg => typecheck_unary(st, ValType::f32(), ValType::f32())
    F32Ceil => typecheck_unary(st, ValType::f32(), ValType::f32())
    F32Floor => typecheck_unary(st, ValType::f32(), ValType::f32())
    F32Trunc => typecheck_unary(st, ValType::f32(), ValType::f32())
    F32Nearest => typecheck_unary(st, ValType::f32(), ValType::f32())
    F32Sqrt => typecheck_unary(st, ValType::f32(), ValType::f32())

    // ===== f32 binary =====
    F32Add => typecheck_binary(st, ValType::f32(), ValType::f32())
    F32Sub => typecheck_binary(st, ValType::f32(), ValType::f32())
    F32Mul => typecheck_binary(st, ValType::f32(), ValType::f32())
    F32Div => typecheck_binary(st, ValType::f32(), ValType::f32())
    F32Min => typecheck_binary(st, ValType::f32(), ValType::f32())
    F32Max => typecheck_binary(st, ValType::f32(), ValType::f32())
    F32Copysign => typecheck_binary(st, ValType::f32(), ValType::f32())

    // ===== f64 unary =====
    F64Abs => typecheck_unary(st, ValType::f64(), ValType::f64())
    F64Neg => typecheck_unary(st, ValType::f64(), ValType::f64())
    F64Ceil => typecheck_unary(st, ValType::f64(), ValType::f64())
    F64Floor => typecheck_unary(st, ValType::f64(), ValType::f64())
    F64Trunc => typecheck_unary(st, ValType::f64(), ValType::f64())
    F64Nearest => typecheck_unary(st, ValType::f64(), ValType::f64())
    F64Sqrt => typecheck_unary(st, ValType::f64(), ValType::f64())

    // ===== f64 binary =====
    F64Add => typecheck_binary(st, ValType::f64(), ValType::f64())
    F64Sub => typecheck_binary(st, ValType::f64(), ValType::f64())
    F64Mul => typecheck_binary(st, ValType::f64(), ValType::f64())
    F64Div => typecheck_binary(st, ValType::f64(), ValType::f64())
    F64Min => typecheck_binary(st, ValType::f64(), ValType::f64())
    F64Max => typecheck_binary(st, ValType::f64(), ValType::f64())
    F64Copysign => typecheck_binary(st, ValType::f64(), ValType::f64())

    // ===== conversions / reinterprets =====

    // i32.wrap_i64 : i64 -> i32
    I32WrapI64 => typecheck_unary(st, ValType::i64(), ValType::i32())

    // i32.trunc_f32/f64_{s,u} : f32/f64 -> i32
    I32TruncF32S => typecheck_unary(st, ValType::f32(), ValType::i32())
    I32TruncF32U => typecheck_unary(st, ValType::f32(), ValType::i32())
    I32TruncF64S => typecheck_unary(st, ValType::f64(), ValType::i32())
    I32TruncF64U => typecheck_unary(st, ValType::f64(), ValType::i32())

    // i64.extend_i32_{s,u} : i32 -> i64
    I64ExtendI32S => typecheck_unary(st, ValType::i32(), ValType::i64())
    I64ExtendI32U => typecheck_unary(st, ValType::i32(), ValType::i64())

    // i64.trunc_f32/f64_{s,u} : f32/f64 -> i64
    I64TruncF32S => typecheck_unary(st, ValType::f32(), ValType::i64())
    I64TruncF32U => typecheck_unary(st, ValType::f32(), ValType::i64())
    I64TruncF64S => typecheck_unary(st, ValType::f64(), ValType::i64())
    I64TruncF64U => typecheck_unary(st, ValType::f64(), ValType::i64())

    // f32.convert_i32/i64_{s,u} : i32/i64 -> f32
    F32ConvertI32S => typecheck_unary(st, ValType::i32(), ValType::f32())
    F32ConvertI32U => typecheck_unary(st, ValType::i32(), ValType::f32())
    F32ConvertI64S => typecheck_unary(st, ValType::i64(), ValType::f32())
    F32ConvertI64U => typecheck_unary(st, ValType::i64(), ValType::f32())

    // f32.demote_f64 : f64 -> f32
    F32DemoteF64 => typecheck_unary(st, ValType::f64(), ValType::f32())

    // f64.convert_i32/i64_{s,u} : i32/i64 -> f64
    F64ConvertI32S => typecheck_unary(st, ValType::i32(), ValType::f64())
    F64ConvertI32U => typecheck_unary(st, ValType::i32(), ValType::f64())
    F64ConvertI64S => typecheck_unary(st, ValType::i64(), ValType::f64())
    F64ConvertI64U => typecheck_unary(st, ValType::i64(), ValType::f64())

    // f64.promote_f32 : f32 -> f64
    F64PromoteF32 => typecheck_unary(st, ValType::f32(), ValType::f64())

    // reinterprets
    I32ReinterpretF32 => typecheck_unary(st, ValType::f32(), ValType::i32())
    I64ReinterpretF64 => typecheck_unary(st, ValType::f64(), ValType::i64())
    F32ReinterpretI32 => typecheck_unary(st, ValType::i32(), ValType::f32())
    F64ReinterpretI64 => typecheck_unary(st, ValType::i64(), ValType::f64())

    // sign-extension (unary)
    I32Extend8S => typecheck_unary(st, ValType::i32(), ValType::i32())
    I32Extend16S => typecheck_unary(st, ValType::i32(), ValType::i32())
    I64Extend8S => typecheck_unary(st, ValType::i64(), ValType::i64())
    I64Extend16S => typecheck_unary(st, ValType::i64(), ValType::i64())
    I64Extend32S => typecheck_unary(st, ValType::i64(), ValType::i64())

    // saturating trunc (same typing as trunc)
    I32TruncSatF32S => typecheck_unary(st, ValType::f32(), ValType::i32())
    I32TruncSatF32U => typecheck_unary(st, ValType::f32(), ValType::i32())
    I32TruncSatF64S => typecheck_unary(st, ValType::f64(), ValType::i32())
    I32TruncSatF64U => typecheck_unary(st, ValType::f64(), ValType::i32())
    I64TruncSatF32S => typecheck_unary(st, ValType::f32(), ValType::i64())
    I64TruncSatF32U => typecheck_unary(st, ValType::f32(), ValType::i64())
    I64TruncSatF64S => typecheck_unary(st, ValType::f64(), ValType::i64())
    I64TruncSatF64U => typecheck_unary(st, ValType::f64(), ValType::i64())
    V128Not => typecheck_v128_unary(st)
    V128And => typecheck_v128_binary(st)
    V128Andnot => typecheck_v128_binary(st)
    V128Or => typecheck_v128_binary(st)
    V128Xor => typecheck_v128_binary(st)
    V128Bitselect => typecheck_v128_ternary(st)
    V128AnyTrue => typecheck_unary(st, ValType::v128(), ValType::i32())

    // Plain v128 load/store
    V128Load(ma) => typecheck_v128_mem_load(st, ma, 128)
    V128Store(ma) => typecheck_v128_mem_store(st, ma, 128)

    // Load and extend (64-bit loads)
    V128Load8x8S(ma) => typecheck_v128_mem_load_extend64(st, ma)
    V128Load8x8U(ma) => typecheck_v128_mem_load_extend64(st, ma)
    V128Load16x4S(ma) => typecheck_v128_mem_load_extend64(st, ma)
    V128Load16x4U(ma) => typecheck_v128_mem_load_extend64(st, ma)
    V128Load32x2S(ma) => typecheck_v128_mem_load_extend64(st, ma)
    V128Load32x2U(ma) => typecheck_v128_mem_load_extend64(st, ma)

    // Splat loads
    V128Load8Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 8)
    V128Load16Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 16)
    V128Load32Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 32)
    V128Load64Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 64)

    // Lane loads
    V128Load8Lane(ma, lane) =>
      typecheck_v128_load_lane(st, ma, 8, lane, b'\x10')
    V128Load16Lane(ma, lane) =>
      typecheck_v128_load_lane(st, ma, 16, lane, b'\x08')
    V128Load32Lane(ma, lane) =>
      typecheck_v128_load_lane(st, ma, 32, lane, b'\x04')
    V128Load64Lane(ma, lane) =>
      typecheck_v128_load_lane(st, ma, 64, lane, b'\x02')

    // Lane stores
    V128Store8Lane(ma, lane) =>
      typecheck_v128_store_lane(st, ma, 8, lane, b'\x10')
    V128Store16Lane(ma, lane) =>
      typecheck_v128_store_lane(st, ma, 16, lane, b'\x08')
    V128Store32Lane(ma, lane) =>
      typecheck_v128_store_lane(st, ma, 32, lane, b'\x04')
    V128Store64Lane(ma, lane) =>
      typecheck_v128_store_lane(st, ma, 64, lane, b'\x02')

    // load*_zero
    V128Load32Zero(ma) => typecheck_v128_mem_load_zero(st, ma, 32)
    V128Load64Zero(ma) => typecheck_v128_mem_load_zero(st, ma, 64)
    V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
      typecheck_v128_const(st)
    I8x16Shuffle(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
      // v128 v128 -> v128
      typecheck_v128_binary(st)
    I8x16Swizzle => typecheck_v128_binary(st) // v128 v128 -> v128
    I8x16Splat => typecheck_splat(st, ValType::i32()) // i32 -> v128 (only low 8 bits used)
    I16x8Splat => typecheck_splat(st, ValType::i32()) // i32 -> v128
    I32x4Splat => typecheck_splat(st, ValType::i32()) // i32 -> v128
    I64x2Splat => typecheck_splat(st, ValType::i64()) // i64 -> v128
    F32x4Splat => typecheck_splat(st, ValType::f32()) // f32 -> v128
    F64x2Splat => typecheck_splat(st, ValType::f64()) // f64 -> v128
    I8x16ExtractLaneS(lane) =>
      typecheck_lane_extract(st, ValType::i32(), lane, b'\x10')
    I8x16ExtractLaneU(lane) =>
      typecheck_lane_extract(st, ValType::i32(), lane, b'\x10')
    I16x8ExtractLaneS(lane) =>
      typecheck_lane_extract(st, ValType::i32(), lane, b'\x08')
    I16x8ExtractLaneU(lane) =>
      typecheck_lane_extract(st, ValType::i32(), lane, b'\x08')
    I32x4ExtractLane(lane) =>
      typecheck_lane_extract(st, ValType::i32(), lane, b'\x04')
    I64x2ExtractLane(lane) =>
      typecheck_lane_extract(st, ValType::i64(), lane, b'\x02')
    F32x4ExtractLane(lane) =>
      typecheck_lane_extract(st, ValType::f32(), lane, b'\x04')
    F64x2ExtractLane(lane) =>
      typecheck_lane_extract(st, ValType::f64(), lane, b'\x02')
    I8x16ReplaceLane(lane) =>
      typecheck_lane_replace(st, ValType::i32(), lane, b'\x10')
    I16x8ReplaceLane(lane) =>
      typecheck_lane_replace(st, ValType::i32(), lane, b'\x08')
    I32x4ReplaceLane(lane) =>
      typecheck_lane_replace(st, ValType::i32(), lane, b'\x04')
    I64x2ReplaceLane(lane) =>
      typecheck_lane_replace(st, ValType::i64(), lane, b'\x02')
    F32x4ReplaceLane(lane) =>
      typecheck_lane_replace(st, ValType::f32(), lane, b'\x04')
    F64x2ReplaceLane(lane) =>
      typecheck_lane_replace(st, ValType::f64(), lane, b'\x02')
    I8x16Eq => typecheck_v128_compare(st)
    I8x16Ne => typecheck_v128_compare(st)
    I8x16LtS => typecheck_v128_compare(st)
    I8x16LtU => typecheck_v128_compare(st)
    I8x16GtS => typecheck_v128_compare(st)
    I8x16GtU => typecheck_v128_compare(st)
    I8x16LeS => typecheck_v128_compare(st)
    I8x16LeU => typecheck_v128_compare(st)
    I8x16GeS => typecheck_v128_compare(st)
    I8x16GeU => typecheck_v128_compare(st)
    I16x8Eq => typecheck_v128_compare(st)
    I16x8Ne => typecheck_v128_compare(st)
    I16x8LtS => typecheck_v128_compare(st)
    I16x8LtU => typecheck_v128_compare(st)
    I16x8GtS => typecheck_v128_compare(st)
    I16x8GtU => typecheck_v128_compare(st)
    I16x8LeS => typecheck_v128_compare(st)
    I16x8LeU => typecheck_v128_compare(st)
    I16x8GeS => typecheck_v128_compare(st)
    I16x8GeU => typecheck_v128_compare(st)
    I32x4Eq => typecheck_v128_compare(st)
    I32x4Ne => typecheck_v128_compare(st)
    I32x4LtS => typecheck_v128_compare(st)
    I32x4LtU => typecheck_v128_compare(st)
    I32x4GtS => typecheck_v128_compare(st)
    I32x4GtU => typecheck_v128_compare(st)
    I32x4LeS => typecheck_v128_compare(st)
    I32x4LeU => typecheck_v128_compare(st)
    I32x4GeS => typecheck_v128_compare(st)
    I32x4GeU => typecheck_v128_compare(st)
    F32x4Eq => typecheck_v128_compare(st)
    F32x4Ne => typecheck_v128_compare(st)
    F32x4Lt => typecheck_v128_compare(st)
    F32x4Gt => typecheck_v128_compare(st)
    F32x4Le => typecheck_v128_compare(st)
    F32x4Ge => typecheck_v128_compare(st)
    F64x2Eq => typecheck_v128_compare(st)
    F64x2Ne => typecheck_v128_compare(st)
    F64x2Lt => typecheck_v128_compare(st)
    F64x2Gt => typecheck_v128_compare(st)
    F64x2Le => typecheck_v128_compare(st)
    F64x2Ge => typecheck_v128_compare(st)
    I8x16Abs => typecheck_v128_unary(st)
    I8x16Neg => typecheck_v128_unary(st)
    I8x16Popcnt => typecheck_v128_unary(st)
    I8x16AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
    I8x16Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
    I8x16NarrowI16x8S => typecheck_v128_binary(st)
    I8x16NarrowI16x8U => typecheck_v128_binary(st)
    I8x16Shl => typecheck_v128_shift(st)
    I8x16ShrS => typecheck_v128_shift(st)
    I8x16ShrU => typecheck_v128_shift(st)
    I8x16Add => typecheck_v128_binary(st)
    I8x16AddSatS => typecheck_v128_binary(st)
    I8x16AddSatU => typecheck_v128_binary(st)
    I8x16Sub => typecheck_v128_binary(st)
    I8x16SubSatS => typecheck_v128_binary(st)
    I8x16SubSatU => typecheck_v128_binary(st)
    I8x16MinS => typecheck_v128_binary(st)
    I8x16MinU => typecheck_v128_binary(st)
    I8x16MaxS => typecheck_v128_binary(st)
    I8x16MaxU => typecheck_v128_binary(st)
    I8x16AvgrU => typecheck_v128_binary(st)
    I16x8Abs => typecheck_v128_unary(st)
    I16x8Neg => typecheck_v128_unary(st)
    I16x8Q15mulrSatS => typecheck_v128_binary(st)
    I16x8AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
    I16x8Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
    I16x8NarrowI32x4S => typecheck_v128_binary(st)
    I16x8NarrowI32x4U => typecheck_v128_binary(st)
    I16x8ExtendLowI8x16S => typecheck_v128_unary(st)
    I16x8ExtendHighI8x16S => typecheck_v128_unary(st)
    I16x8ExtendLowI8x16U => typecheck_v128_unary(st)
    I16x8ExtendHighI8x16U => typecheck_v128_unary(st)
    I16x8Shl => typecheck_v128_shift(st)
    I16x8ShrS => typecheck_v128_shift(st)
    I16x8ShrU => typecheck_v128_shift(st)
    I16x8Add => typecheck_v128_binary(st)
    I16x8AddSatS => typecheck_v128_binary(st)
    I16x8AddSatU => typecheck_v128_binary(st)
    I16x8Sub => typecheck_v128_binary(st)
    I16x8SubSatS => typecheck_v128_binary(st)
    I16x8SubSatU => typecheck_v128_binary(st)
    I16x8Mul => typecheck_v128_binary(st)
    I16x8MinS => typecheck_v128_binary(st)
    I16x8MinU => typecheck_v128_binary(st)
    I16x8MaxS => typecheck_v128_binary(st)
    I16x8MaxU => typecheck_v128_binary(st)
    I16x8AvgrU => typecheck_v128_binary(st)
    I32x4Abs => typecheck_v128_unary(st)
    I32x4Neg => typecheck_v128_unary(st)
    I32x4AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
    I32x4Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
    I32x4ExtendLowI16x8S => typecheck_v128_unary(st)
    I32x4ExtendHighI16x8S => typecheck_v128_unary(st)
    I32x4ExtendLowI16x8U => typecheck_v128_unary(st)
    I32x4ExtendHighI16x8U => typecheck_v128_unary(st)
    I32x4Shl => typecheck_v128_shift(st)
    I32x4ShrS => typecheck_v128_shift(st)
    I32x4ShrU => typecheck_v128_shift(st)
    I32x4Add => typecheck_v128_binary(st)
    I32x4Sub => typecheck_v128_binary(st)
    I32x4Mul => typecheck_v128_binary(st)
    I32x4MinS => typecheck_v128_binary(st)
    I32x4MinU => typecheck_v128_binary(st)
    I32x4MaxS => typecheck_v128_binary(st)
    I32x4MaxU => typecheck_v128_binary(st)
    I32x4DotI16x8S => typecheck_v128_binary(st)
    I64x2Abs => typecheck_v128_unary(st)
    I64x2Neg => typecheck_v128_unary(st)
    I64x2AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
    I64x2Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
    I64x2Shl => typecheck_v128_shift(st)
    I64x2ShrS => typecheck_v128_shift(st)
    I64x2ShrU => typecheck_v128_shift(st)
    I64x2Add => typecheck_v128_binary(st)
    I64x2Sub => typecheck_v128_binary(st)
    I64x2Mul => typecheck_v128_binary(st)
    I64x2Eq => typecheck_v128_compare(st)
    I64x2Ne => typecheck_v128_compare(st)
    I64x2LtS => typecheck_v128_compare(st)
    I64x2GtS => typecheck_v128_compare(st)
    I64x2LeS => typecheck_v128_compare(st)
    I64x2GeS => typecheck_v128_compare(st)
    F32x4Abs => typecheck_v128_unary(st)
    F32x4Neg => typecheck_v128_unary(st)
    F32x4Sqrt => typecheck_v128_unary(st)
    F32x4Add => typecheck_v128_binary(st)
    F32x4Sub => typecheck_v128_binary(st)
    F32x4Mul => typecheck_v128_binary(st)
    F32x4Div => typecheck_v128_binary(st)
    F32x4Min => typecheck_v128_binary(st)
    F32x4Max => typecheck_v128_binary(st)
    F32x4Pmin => typecheck_v128_binary(st)
    F32x4Pmax => typecheck_v128_binary(st)
    F32x4Ceil => typecheck_v128_unary(st)
    F32x4Floor => typecheck_v128_unary(st)
    F32x4Trunc => typecheck_v128_unary(st)
    F32x4Nearest => typecheck_v128_unary(st)
    F64x2Abs => typecheck_v128_unary(st)
    F64x2Neg => typecheck_v128_unary(st)
    F64x2Sqrt => typecheck_v128_unary(st)
    F64x2Add => typecheck_v128_binary(st)
    F64x2Sub => typecheck_v128_binary(st)
    F64x2Mul => typecheck_v128_binary(st)
    F64x2Div => typecheck_v128_binary(st)
    F64x2Min => typecheck_v128_binary(st)
    F64x2Max => typecheck_v128_binary(st)
    F64x2Pmin => typecheck_v128_binary(st)
    F64x2Pmax => typecheck_v128_binary(st)
    F64x2Ceil => typecheck_v128_unary(st)
    F64x2Floor => typecheck_v128_unary(st)
    F64x2Trunc => typecheck_v128_unary(st)
    F64x2Nearest => typecheck_v128_unary(st)
    I32x4TruncSatF32x4S => typecheck_v128_unary(st)
    I32x4TruncSatF32x4U => typecheck_v128_unary(st)
    F32x4ConvertI32x4S => typecheck_v128_unary(st)
    F32x4ConvertI32x4U => typecheck_v128_unary(st)
    I32x4TruncSatF64x2SZero => typecheck_v128_unary(st)
    I32x4TruncSatF64x2UZero => typecheck_v128_unary(st)
    F64x2ConvertLowI32x4S => typecheck_v128_unary(st)
    F64x2ConvertLowI32x4U => typecheck_v128_unary(st)
    F32x4DemoteF64x2Zero => typecheck_v128_unary(st)
    F64x2PromoteLowF32x4 => typecheck_v128_unary(st)
    I8x16RelaxedSwizzle => typecheck_v128_binary(st)
    I32x4RelaxedTruncF32x4S => typecheck_v128_unary(st)
    I32x4RelaxedTruncF32x4U => typecheck_v128_unary(st)
    I32x4RelaxedTruncZeroF64x2S => typecheck_v128_unary(st)
    I32x4RelaxedTruncZeroF64x2U => typecheck_v128_unary(st)
    F32x4RelaxedMadd => typecheck_v128_ternary(st)
    F32x4RelaxedNmadd => typecheck_v128_ternary(st)
    F64x2RelaxedMadd => typecheck_v128_ternary(st)
    F64x2RelaxedNmadd => typecheck_v128_ternary(st)
    I8x16RelaxedLaneselect => typecheck_v128_ternary(st)
    I16x8RelaxedLaneselect => typecheck_v128_ternary(st)
    I32x4RelaxedLaneselect => typecheck_v128_ternary(st)
    I64x2RelaxedLaneselect => typecheck_v128_ternary(st)
    F32x4RelaxedMin => typecheck_v128_binary(st)
    F32x4RelaxedMax => typecheck_v128_binary(st)
    F64x2RelaxedMin => typecheck_v128_binary(st)
    F64x2RelaxedMax => typecheck_v128_binary(st)
    I16x8RelaxedQ15mulrS => typecheck_v128_binary(st)
    I16x8RelaxedDotI8x16I7x16S => typecheck_v128_binary(st)
    I32x4RelaxedDotI8x16I7x16AddS => typecheck_v128_ternary(st)

    // ===== extadd pairwise (unary) =====
    I16x8ExtaddPairwiseI8x16S => typecheck_v128_unary(st)
    I16x8ExtaddPairwiseI8x16U => typecheck_v128_unary(st)
    I32x4ExtaddPairwiseI16x8S => typecheck_v128_unary(st)
    I32x4ExtaddPairwiseI16x8U => typecheck_v128_unary(st)

    // ===== extend (unary) =====
    I64x2ExtendLowI32x4S => typecheck_v128_unary(st)
    I64x2ExtendLowI32x4U => typecheck_v128_unary(st)
    I64x2ExtendHighI32x4S => typecheck_v128_unary(st)
    I64x2ExtendHighI32x4U => typecheck_v128_unary(st)

    // ===== extmul low/high (binary) =====
    I16x8ExtmulLowI8x16S => typecheck_v128_binary(st)
    I16x8ExtmulLowI8x16U => typecheck_v128_binary(st)
    I16x8ExtmulHighI8x16S => typecheck_v128_binary(st)
    I16x8ExtmulHighI8x16U => typecheck_v128_binary(st)
    I32x4ExtmulLowI16x8S => typecheck_v128_binary(st)
    I32x4ExtmulLowI16x8U => typecheck_v128_binary(st)
    I32x4ExtmulHighI16x8S => typecheck_v128_binary(st)
    I32x4ExtmulHighI16x8U => typecheck_v128_binary(st)
    I64x2ExtmulLowI32x4S => typecheck_v128_binary(st)
    I64x2ExtmulLowI32x4U => typecheck_v128_binary(st)
    I64x2ExtmulHighI32x4S => typecheck_v128_binary(st)
    I64x2ExtmulHighI32x4U => typecheck_v128_binary(st)

    // _ => Err("unimplemented")
  }
}

// =============================================================================
// HELPER FUNCTIONS
// =============================================================================

///|
fn assert_stack(state : TcState, expected : Array[ValType]) -> Unit {
  if !equals(state.stack, expected) {
    abort("Stack mismatch: expected \{expected}, got \{state.stack}")
  }
}

///|
fn[T] assert_ok(result : Result[T, String]) -> T {
  match result {
    Ok(v) => v
    Err(e) => abort("Expected Ok, got Err: \{e}")
  }
}

///|
fn[T : Show] assert_err(result : Result[T, String]) -> Unit {
  match result {
    Ok(v) => abort("Expected Err, got Ok: \{v}")
    Err(_) => ()
  }
}

///|
fn anyref_type() -> ValType {
  ValType::ref_type(RefType::abs(AbsHeapType::any()))
}

///|
fn eqref_type() -> ValType {
  ValType::ref_type(RefType::abs(AbsHeapType::eq()))
}

///|
fn i31ref_type() -> ValType {
  ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::i31())))
}

///|
fn nullable_funcref() -> ValType {
  ValType::ref_type(RefType::new(true, HeapType::abs(AbsHeapType::func())))
}

///|
fn nullable_externref() -> ValType {
  ValType::ref_type(RefType::new(true, HeapType::abs(AbsHeapType::extern_())))
}

///|
fn non_nullable_funcref() -> ValType {
  ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::func())))
}

///|
fn non_nullable_externref() -> ValType {
  ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::extern_())))
}

///|
fn tc_state_all_locals_initialized(
  env : Env,
  expanded_slot_visits~ : Array[Int32]? = None,
) -> Array[Bool] {
  ignore(expanded_slot_visits)
  Array::make(env.locals.length(), true)
}

///|
pub fn make_state(env : Env, stack : Array[ValType]) -> TcState {
  let initialized_locals = tc_state_all_locals_initialized(env)
  {
    env,
    stack: stack.copy(),
    reachable: true,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals,
  }
}

///|
pub fn make_state_owned(env : Env, stack : Array[ValType]) -> TcState {
  let initialized_locals = tc_state_all_locals_initialized(env)
  {
    env,
    stack,
    reachable: true,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals,
  }
}

///|
pub fn tc_state_with_stack(state : TcState, stack : Array[ValType]) -> TcState {
  {
    env: state.env,
    stack: stack.copy(),
    reachable: state.reachable,
    polymorphic: state.polymorphic,
    escape: state.escape,
    reachable_escape_depths: state.reachable_escape_depths,
    initialized_locals: state.initialized_locals,
  }
}

///|
pub fn tc_state_clone(state : TcState) -> TcState {
  {
    env: state.env,
    stack: state.stack.copy(),
    reachable: state.reachable,
    polymorphic: state.polymorphic,
    escape: state.escape,
    reachable_escape_depths: state.reachable_escape_depths.copy(),
    initialized_locals: state.initialized_locals.copy(),
  }
}

///|
pub fn tc_state_with_owned_stack(
  state : TcState,
  stack : Array[ValType],
) -> TcState {
  {
    env: state.env,
    stack,
    reachable: state.reachable,
    polymorphic: state.polymorphic,
    escape: state.escape,
    reachable_escape_depths: state.reachable_escape_depths,
    initialized_locals: state.initialized_locals,
  }
}

///|
// Fork a child scope with an owned stack and no inherited escape observations.
// Matching local storage preserves definite-initialization facts. A different
// local environment keeps tc_state_new's established all-initialized policy.
pub fn tc_state_fork_body(
  state : TcState,
  env : Env,
  stack : Array[ValType],
) -> TcState {
  if !physical_equal(state.env.locals, env.locals) {
    return TcState::{
      ..tc_state_new(env, stack, state.reachable, NoTcEscape),
      polymorphic: false,
    }
  }
  TcState::{
    env,
    stack,
    reachable: state.reachable,
    polymorphic: false,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals: state.initialized_locals,
  }
}

///|
pub fn tc_state_new(
  env : Env,
  stack : Array[ValType],
  reachable : Bool,
  escape : TcEscape,
) -> TcState {
  let initialized_locals = tc_state_all_locals_initialized(env)
  {
    env,
    stack,
    reachable,
    polymorphic: !reachable,
    escape,
    reachable_escape_depths: [],
    initialized_locals,
  }
}

///|
pub fn tc_escape_none() -> TcEscape {
  NoTcEscape
}

///|
pub fn tc_escape_terminal() -> TcEscape {
  TerminalTcEscape
}

///|
pub fn tc_state_pop_expect(
  state : TcState,
  expected : ValType,
) -> Result[TcState, String] {
  state.pop_expect(expected)
}

///|
pub fn tc_state_pop_types(
  state : TcState,
  types : Array[ValType],
) -> Result[TcState, String] {
  state.pop_types(types)
}

///|
pub fn tc_state_push_types(state : TcState, types : Array[ValType]) -> TcState {
  state.push_types(types)
}

///|
pub fn tc_state_validate_end_stack(
  state : TcState,
  results : Array[ValType],
) -> Result[Unit, String] {
  validate_end_stack(state, results)
}

///|
fn typecheck_expr_with_escape_summary_for_test(
  expr : Expr,
  state : TcState,
) -> Result[(TcState, Array[Int32]), String] {
  match Typecheck::typecheck(expr, state) {
    Ok(out) => Ok((out, out.reachable_escape_depths))
    Err(err) => Err(err)
  }
}

///|
pub fn tc_state_normalize_untyped_block_exit(
  state : TcState,
  results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
  normalize_untyped_block_exit(state, results)
}

///|
pub fn tc_state_normalize_untyped_if_branch_exit(
  state : TcState,
  results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
  normalize_untyped_if_branch_exit(state, results)
}

///|
test "tc_state_validate_end_stack reports reachable underflow on empty stack" {
  let state = tc_state_new(empty_env(), [], true, NoTcEscape)
  let result = tc_state_validate_end_stack(state, [ValType::i32()])
  @debug.assert_eq(result, Err("stack underflow"))
}

///|
test "tc_state_validate_end_stack accepts unreachable virtual-bottom suffixes" {
  let state = TcState::{
    env: empty_env(),
    stack: [],
    reachable: false,
    polymorphic: true,
    escape: NoTcEscape,
    reachable_escape_depths: [],
    initialized_locals: [],
  }
  let result = tc_state_validate_end_stack(state, [ValType::i32()])
  @debug.assert_eq(result, Ok(()))
}

///|
test "typecheck expression summary records only reachable branch escapes" {
  let env = empty_env().with_label([ValType::i32()])
  let state = make_state(env, [])
  let expr = Expr::new([
    Instruction::i32_const(@lib.I32::new(1)),
    Instruction::i32_const(@lib.I32::new(0)),
    Instruction::br_if(LabelIdx::new(0)),
    Instruction::unreachable_(),
    Instruction::i32_const(@lib.I32::new(2)),
    Instruction::i32_const(@lib.I32::new(0)),
    Instruction::br_if(LabelIdx::new(0)),
  ])
  match typecheck_expr_with_escape_summary_for_test(expr, state) {
    Ok((_, depths)) => @debug.assert_eq(depths, [0])
    Err(err) =>
      fail("expected expression summary typecheck success, got \{err}")
  }

  let nested_state = make_state(empty_env().with_label([ValType::i32()]), [])
  let nested = Expr::new([
    Instruction::block(
      BlockType::void_(),
      Expr::new([
        Instruction::i32_const(@lib.I32::new(1)),
        Instruction::i32_const(@lib.I32::new(0)),
        Instruction::br_if(LabelIdx::new(1)),
        Instruction::drop(),
      ]),
    ),
  ])
  match typecheck_expr_with_escape_summary_for_test(nested, nested_state) {
    Ok((_, depths)) => @debug.assert_eq(depths, [0])
    Err(err) =>
      fail("expected nested expression summary typecheck success, got \{err}")
  }
}

///|
pub fn tc_state_merge_nonfallthrough_if_exits(
  left : TcEscape,
  right : TcEscape,
) -> TcEscape {
  merge_nonfallthrough_if_exits(left, right)
}

///|
pub fn tc_state_typecheck_catch_clause(
  state : TcState,
  catch_ : Catch,
) -> Result[Unit, String] {
  match typecheck_catch_clause(state, catch_) {
    Ok(_) => Ok(())
    Err(err) => Err(err)
  }
}

// =============================================================================
// CONTROL INSTRUCTIONS
// =============================================================================

///|
test "Typecheck Unreachable" {
  let state = make_state(empty_env(), [])
  let t = Instruction::unreachable_()
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck Nop" {
  let state = make_state(empty_env(), [ValType::i32()])
  let t = Instruction::nop()
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck Block empty" {
  let env = empty_env()
  let state = make_state(env, [])
  let block = Instruction::block(BlockType::void_(), Expr::new([]))
  let new_state = assert_ok(Typecheck::typecheck(block, state))
  assert_stack(new_state, [])
}

///|
test "Typecheck Block with result" {
  let env = empty_env()
  let state = make_state(env, [])
  let block = Instruction::block(
    BlockType::val_type(ValType::i32()),
    Expr::new([Instruction::i32_const(I32(42))]),
  )
  let new_state = assert_ok(Typecheck::typecheck(block, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck Block preserves outer stack" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64()])
  let block = Instruction::block(
    BlockType::val_type(ValType::i32()),
    Expr::new([Instruction::i32_const(I32(1))]),
  )
  let new_state = assert_ok(Typecheck::typecheck(block, state))
  assert_stack(new_state, [ValType::i64(), ValType::i32()])
}

///|
test "Typecheck Loop empty" {
  let env = empty_env()
  let state = make_state(env, [])
  let loop_instr = Instruction::loop_(BlockType::void_(), Expr::new([]))
  let new_state = assert_ok(Typecheck::typecheck(loop_instr, state))
  assert_stack(new_state, [])
}

///|
test "Typecheck If without else" {
  let env = empty_env()
  let state = make_state(env, [ValType::i32()])
  let if_instr = Instruction::if_(
    BlockType::void_(),
    [Instruction::nop()],
    None,
  )
  let new_state = assert_ok(Typecheck::typecheck(if_instr, state))
  assert_stack(new_state, [])
}

///|
test "Typecheck If with else" {
  let env = empty_env()
  let state = make_state(env, [ValType::i32()])
  let if_instr = Instruction::if_(
    BlockType::val_type(ValType::i32()),
    [Instruction::i32_const(I32(1))],
    Some([Instruction::i32_const(I32(2))]),
  )
  let new_state = assert_ok(Typecheck::typecheck(if_instr, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck If fails without condition" {
  let env = empty_env()
  let state = make_state(env, [])
  let if_instr = Instruction::if_(
    BlockType::void_(),
    [Instruction::nop()],
    None,
  )
  assert_err(Typecheck::typecheck(if_instr, state))
}

///|
test "Typecheck loop accepts nested if branches that escape to the loop and function return" {
  let env = empty_env().with_return_type(Some([ValType::i32()]))
  let state = make_state(env, [])
  let loop_instr = Instruction::loop_(
    BlockType::val_type(ValType::i32()),
    Expr::new([
      Instruction::i32_const(I32(1)),
      Instruction::if_(
        BlockType::void_(),
        [
          Instruction::i32_const(I32(1)),
          Instruction::if_(
            BlockType::void_(),
            [Instruction::br(LabelIdx::new(2))],
            Some([Instruction::i32_const(I32(0)), Instruction::return_()]),
          ),
        ],
        Some([Instruction::i32_const(I32(1)), Instruction::return_()]),
      ),
      // Closing a child frame does not make this frame polymorphic.
      Instruction::unreachable_(),
    ]),
  )
  let new_state = assert_ok(Typecheck::typecheck(loop_instr, state))
  assert_false(new_state.reachable)
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck Br" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::br(LabelIdx::new(0)), state),
  )
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck Br with values" {
  let env = empty_env().with_labels([[ValType::i32()]])
  let state = make_state(env, [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::br(LabelIdx::new(0)), state),
  )
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck Br invalid label" {
  let env = empty_env().with_labels([])
  let state = make_state(env, [])
  let t = Instruction::br(LabelIdx::new(0))
  assert_err(Typecheck::typecheck(t, state))
}

///|
test "Typecheck BrIf" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [ValType::i32()])
  let t = Instruction::br_if(LabelIdx::new(0))
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [])
}

///|
test "Typecheck BrIf with values" {
  let env = empty_env().with_labels([[ValType::i32()]])
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let t = Instruction::br_if(LabelIdx::new(0))
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck BrTable" {
  let env = empty_env().with_labels([[], []])
  let state = make_state(env, [ValType::i32()])
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  let new_state = assert_ok(Typecheck::typecheck(br_table, state))
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck BrTable with values" {
  // Labels expect an i32 value to be passed
  let env = empty_env().with_labels([[ValType::i32()], [ValType::i32()]])
  // Stack: [i32 (branch value), i32 (selector)]
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  let new_state = assert_ok(Typecheck::typecheck(br_table, state))
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck BrTable with multiple values" {
  // Labels expect i32, i64 to be passed
  let env = empty_env().with_labels([
    [ValType::i32(), ValType::i64()],
    [ValType::i32(), ValType::i64()],
  ])
  // Stack: [i32, i64 (branch values), i32 (selector)]
  let state = make_state(env, [ValType::i32(), ValType::i64(), ValType::i32()])
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  let new_state = assert_ok(Typecheck::typecheck(br_table, state))
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck BrTable multiple targets" {
  let env = empty_env().with_labels([[], [], [], []])
  let state = make_state(env, [ValType::i32()])
  // Jump table with 3 entries plus default
  let br_table = Instruction::br_table(
    [LabelIdx::new(0), LabelIdx::new(1), LabelIdx::new(2)],
    LabelIdx::new(3),
  )
  let new_state = assert_ok(Typecheck::typecheck(br_table, state))
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck BrTable invalid default label" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [ValType::i32()])
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(5)) // invalid default
  assert_err(Typecheck::typecheck(br_table, state))
}

///|
test "Typecheck BrTable invalid table label" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [ValType::i32()])
  let br_table = Instruction::br_table(
    [LabelIdx::new(0), LabelIdx::new(99)],
    LabelIdx::new(0),
  ) // invalid in table
  assert_err(Typecheck::typecheck(br_table, state))
}

///|
test "Typecheck BrTable mismatched label types" {
  // Labels have different arities - this is invalid
  let env = empty_env().with_labels([[ValType::i32()], []])
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  assert_err(Typecheck::typecheck(br_table, state))
}

///|
test "Typecheck BrTable mismatched label value types" {
  // Labels have same arity but different types
  let env = empty_env().with_labels([[ValType::i32()], [ValType::i64()]])
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  assert_err(Typecheck::typecheck(br_table, state))
}

///|
test "Typecheck BrTable missing selector" {
  let env = empty_env().with_labels([[], []])
  let state = make_state(env, []) // empty stack - no selector
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  assert_err(Typecheck::typecheck(br_table, state))
}

///|
test "Typecheck BrTable missing branch values" {
  // Labels expect an i32 but stack only has the selector
  let env = empty_env().with_labels([[ValType::i32()], [ValType::i32()]])
  let state = make_state(env, [ValType::i32()]) // only selector, no branch value
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  assert_err(Typecheck::typecheck(br_table, state))
}

///|
test "Typecheck BrTable wrong selector type" {
  let env = empty_env().with_labels([[], []])
  let state = make_state(env, [ValType::i64()]) // wrong type for selector
  let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
  assert_err(Typecheck::typecheck(br_table, state))
}

///|
test "Typecheck Return" {
  let env = empty_env().with_return_type(Some([]))
  let state = make_state(env, [])
  let t = Instruction::return_()
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  @debug.assert_eq(new_state.reachable, false)
}

///|
test "Typecheck Return with value" {
  let env = empty_env().with_return_type(Some([ValType::i32()]))
  let state = make_state(env, [ValType::i32()])
  let t = Instruction::return_()
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  @debug.assert_eq(new_state.reachable, false)
}

// =============================================================================
// PARAMETRIC INSTRUCTIONS
// =============================================================================

///|
test "Typecheck Drop i32" {
  let state = make_state(empty_env(), [ValType::i32()])
  let t = Instruction::drop()
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [])
}

///|
test "Typecheck Drop i64" {
  let state = make_state(empty_env(), [ValType::i64()])
  let t = Instruction::drop()
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [])
}

///|
test "Typecheck Drop fails empty stack" {
  let state = make_state(empty_env(), [])
  let t = Instruction::drop()
  assert_err(Typecheck::typecheck(t, state))
}

///|
test "Typecheck Select without annotation" {
  let state = make_state(empty_env(), [
    ValType::i32(),
    ValType::i32(),
    ValType::i32(),
  ])
  let t = Instruction::select(types=None)
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck Select with annotation" {
  let state = make_state(empty_env(), [
    ValType::i32(),
    ValType::i32(),
    ValType::i32(),
  ])
  let t = Instruction::select(types=Some([ValType::i32()]))
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck Select type mismatch fails" {
  let state = make_state(empty_env(), [
    ValType::i32(),
    ValType::i64(),
    ValType::i32(),
  ])
  let t = Instruction::select(types=None)
  assert_err(Typecheck::typecheck(t, state))
}

// =============================================================================
// VARIABLE INSTRUCTIONS
// =============================================================================

///|
test "Typecheck LocalGet" {
  let env : Env = {
    ..empty_env(),
    locals: Locals::from_types([ValType::i32()]),
  }
  let state = make_state(env, [])
  let t = Instruction::local_get(LocalIdx::new(0))
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck LocalGet invalid index" {
  let env : Env = { ..empty_env(), locals: Locals::empty(), }
  let state = make_state(env, [])
  let t = Instruction::local_get(LocalIdx::new(0))
  assert_err(Typecheck::typecheck(t, state))
}

///|
test "Typecheck LocalSet" {
  let env : Env = {
    ..empty_env(),
    locals: Locals::from_types([ValType::i32()]),
  }
  let state = make_state(env, [ValType::i32()])
  let t = Instruction::local_set(LocalIdx::new(0))
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [])
}

///|
test "Typecheck LocalSet type mismatch" {
  let env = Env::{
    ..empty_env(),
    locals: Locals::from_types([ValType::i32()]),
  }
  let state = make_state(env, [ValType::i64()])
  let t = Instruction::local_set(LocalIdx::new(0))
  assert_err(Typecheck::typecheck(t, state))
}

///|
test "Typecheck LocalTee" {
  let env : Env = {
    ..empty_env(),
    locals: Locals::from_types([ValType::i32()]),
  }
  let state = make_state(env, [ValType::i32()])
  let t = Instruction::local_tee(LocalIdx::new(0))
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck GlobalGet" {
  let env : Env = {
    ..empty_env(),
    globals: [GlobalType::new(ValType::i32(), false)],
  }
  let state = make_state(env, [])
  let t = Instruction::global_get(GlobalIdx::new(0))
  let new_state = assert_ok(Typecheck::typecheck(t, state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck GlobalSet mutable" {
  let env : Env = {
    ..empty_env(),
    globals: [GlobalType::new(ValType::i32(), true)],
  }
  let state = make_state(env, [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::global_set(GlobalIdx::new(0)), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck GlobalSet immutable fails" {
  let env : Env = {
    ..empty_env(),
    globals: [GlobalType::new(ValType::i32(), false)],
  }
  let state = make_state(env, [ValType::i32()])
  assert_err(
    Typecheck::typecheck(Instruction::global_set(GlobalIdx::new(0)), state),
  )
}

// =============================================================================
// TABLE INSTRUCTIONS
// =============================================================================

///|
test "Typecheck TableGet" {
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(0, None),
  )
  let env : Env = { ..empty_env(), tables: [table_type], }
  let state = make_state(env, [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::table_get(TableIdx::new(0)), state),
  )
  assert_stack(new_state, [nullable_funcref()])
}

///|
test "Typecheck TableSet" {
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(0, None),
  )
  let env : Env = { ..empty_env(), tables: [table_type], }
  let state = make_state(env, [ValType::i32(), nullable_funcref()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::table_set(TableIdx::new(0)), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck TableSize" {
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(0, None),
  )
  let env : Env = { ..empty_env(), tables: [table_type], }
  let state = make_state(env, [])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::table_size(TableIdx::new(0)), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck TableGrow" {
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(0, None),
  )
  let env : Env = { ..empty_env(), tables: [table_type], }
  let state = make_state(env, [nullable_funcref(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::table_grow(TableIdx::new(0)), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck TableFill" {
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(0, None),
  )
  let env : Env = { ..empty_env(), tables: [table_type], }
  let state = make_state(env, [
    ValType::i32(),
    nullable_funcref(),
    ValType::i32(),
  ])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::table_fill(TableIdx::new(0)), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck TableCopy" {
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(0, None),
  )
  let env : Env = { ..empty_env(), tables: [table_type, table_type], }
  let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::table_copy(TableIdx::new(0), TableIdx::new(1)),
      state,
    ),
  )
  assert_stack(new_state, [])
}

// =============================================================================
// MEMORY INSTRUCTIONS
// =============================================================================

///|
test "Typecheck I32Load" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_load(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64Load" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_load(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck F32Load" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_load(mem_arg), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F64Load" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64_load(mem_arg), state),
  )
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck I32Load8S" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_load8s(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Load8U" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_load8u(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Load16S" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_load16s(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Load16U" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_load16u(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64Load8S" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_load8s(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64Load32S" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_load32s(mem_arg), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I32Store" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_store(mem_arg), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck I64Store" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::i64()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_store(mem_arg), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck F32Store" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::f32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_store(mem_arg), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck F64Store" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::f64()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64_store(mem_arg), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck I32Store8" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_store8(mem_arg), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck I64Store32" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::i64()])
  let mem_arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_store32(mem_arg), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck MemorySize" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::memory_size(MemIdx::new(0)), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck MemoryGrow" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::memory_grow(MemIdx::new(0)), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck MemoryCopy" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::memory_copy(MemIdx::new(0), MemIdx::new(0)),
      state,
    ),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck MemoryFill" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::memory_fill(MemIdx::new(0)), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck MemoryFill memory64 length uses address type" {
  let env : Env = {
    ..empty_env(),
    mems: [MemType::new(Limits::i64(1UL, None))],
  }
  let state = make_state(env, [ValType::i64(), ValType::i32(), ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::memory_fill(MemIdx::new(0)), state),
  )
  assert_stack(new_state, [])
}

// =============================================================================
// NUMERIC CONSTANTS
// =============================================================================

///|
test "Typecheck I32Const" {
  let state = make_state(empty_env(), [])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_const(I32(42)), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64Const" {
  let state = make_state(empty_env(), [])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_const(I64(42L)), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck F32Const" {
  let state = make_state(empty_env(), [])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_const(F32(3.14)), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F64Const" {
  let state = make_state(empty_env(), [])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64_const(F64(3.14)), state),
  )
  assert_stack(new_state, [ValType::f64()])
}

// =============================================================================
// I32 COMPARISON OPERATIONS
// =============================================================================

///|
test "Typecheck I32Eqz" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_eqz(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Eq" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_eq(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Ne" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_ne(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32LtS" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_lt_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32LtU" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_lt_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32GtS" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_gt_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32GtU" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_gt_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32LeS" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_le_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32LeU" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_le_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32GeS" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_ge_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32GeU" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_ge_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

// =============================================================================
// I64 COMPARISON OPERATIONS
// =============================================================================

///|
test "Typecheck I64Eqz" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_eqz(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64Eq" {
  let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_eq(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64Ne" {
  let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_ne(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64LtS" {
  let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_lt_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64GtU" {
  let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_gt_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

// =============================================================================
// F32 COMPARISON OPERATIONS
// =============================================================================

///|
test "Typecheck F32Eq" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_eq(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck F32Ne" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_ne(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck F32Lt" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_lt(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck F32Gt" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_gt(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck F32Le" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_le(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck F32Ge" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_ge(), state))
  assert_stack(new_state, [ValType::i32()])
}

// =============================================================================
// F64 COMPARISON OPERATIONS
// =============================================================================

///|
test "Typecheck F64Eq" {
  let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_eq(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck F64Lt" {
  let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_lt(), state))
  assert_stack(new_state, [ValType::i32()])
}

// =============================================================================
// I32 ARITHMETIC OPERATIONS
// =============================================================================

///|
test "Typecheck I32Clz" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_clz(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Ctz" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_ctz(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Popcnt" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_popcnt(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Add" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_add(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Sub" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_sub(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Mul" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_mul(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32DivS" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_div_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32DivU" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_div_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32RemS" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_rem_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32RemU" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_rem_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32And" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_and(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Or" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_or(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Xor" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_xor(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Shl" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_shl(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32ShrS" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_shr_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32ShrU" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_shr_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Rotl" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_rotl(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Rotr" {
  let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_rotr(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

// =============================================================================
// I64 ARITHMETIC OPERATIONS
// =============================================================================

///|
test "Typecheck I64Clz" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_clz(), state))
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64Add" {
  let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_add(), state))
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64Sub" {
  let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_sub(), state))
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64Mul" {
  let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_mul(), state))
  assert_stack(new_state, [ValType::i64()])
}

// =============================================================================
// F32 ARITHMETIC OPERATIONS
// =============================================================================

///|
test "Typecheck F32Abs" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_abs(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Neg" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_neg(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Ceil" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_ceil(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Floor" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_floor(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Sqrt" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_sqrt(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Add" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_add(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Sub" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_sub(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Mul" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_mul(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Div" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_div(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Min" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_min(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Max" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_max(), state))
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32Copysign" {
  let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_copysign(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

// =============================================================================
// F64 ARITHMETIC OPERATIONS
// =============================================================================

///|
test "Typecheck F64Abs" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_abs(), state))
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck F64Neg" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_neg(), state))
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck F64Add" {
  let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_add(), state))
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck F64Sub" {
  let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_sub(), state))
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck F64Mul" {
  let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_mul(), state))
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck F64Div" {
  let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_div(), state))
  assert_stack(new_state, [ValType::f64()])
}

// =============================================================================
// CONVERSION OPERATIONS
// =============================================================================

///|
test "Typecheck I32WrapI64" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_wrap_i64(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32TruncF32S" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_trunc_f32s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32TruncF32U" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_trunc_f32u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32TruncF64S" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_trunc_f64s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32TruncF64U" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_trunc_f64u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64ExtendI32S" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_extend_i32s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64ExtendI32U" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_extend_i32u(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64TruncF32S" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_trunc_f32s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64TruncF64S" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_trunc_f64s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck F32ConvertI32S" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_convert_i32s(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32ConvertI32U" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_convert_i32u(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32ConvertI64S" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_convert_i64s(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F32DemoteF64" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_demote_f64(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F64ConvertI32S" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64_convert_i32s(), state),
  )
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck F64ConvertI64S" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64_convert_i64s(), state),
  )
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck F64PromoteF32" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64_promote_f32(), state),
  )
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck I32ReinterpretF32" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_reinterpret_f32(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64ReinterpretF64" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_reinterpret_f64(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck F32ReinterpretI32" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32_reinterpret_i32(), state),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F64ReinterpretI64" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64_reinterpret_i64(), state),
  )
  assert_stack(new_state, [ValType::f64()])
}

// =============================================================================
// SIGN EXTENSION OPERATIONS
// =============================================================================

///|
test "Typecheck I32Extend8S" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_extend8s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32Extend16S" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_extend16s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64Extend8S" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_extend8s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64Extend16S" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_extend16s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64Extend32S" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_extend32s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

// =============================================================================
// SATURATING TRUNCATION
// =============================================================================

///|
test "Typecheck I32TruncSatF32S" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_trunc_sat_f32s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32TruncSatF32U" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_trunc_sat_f32u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32TruncSatF64S" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32_trunc_sat_f64s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64TruncSatF32S" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_trunc_sat_f32s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck I64TruncSatF64S" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64_trunc_sat_f64s(), state),
  )
  assert_stack(new_state, [ValType::i64()])
}

// =============================================================================
// REFERENCE INSTRUCTIONS
// =============================================================================

///|
test "Typecheck RefNull func" {
  let state = make_state(empty_env(), [])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::ref_null(HeapType::abs(AbsHeapType::func())),
      state,
    ),
  )
  assert_stack(new_state, [nullable_funcref()])
}

///|
test "Typecheck RefNull extern" {
  let state = make_state(empty_env(), [])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::ref_null(HeapType::abs(AbsHeapType::extern_())),
      state,
    ),
  )
  assert_stack(new_state, [nullable_externref()])
}

///|
test "Typecheck RefIsNull" {
  let state = make_state(empty_env(), [nullable_funcref()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::ref_is_null(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck RefIsNull accepts non-null references" {
  let state = make_state(empty_env(), [ValType::i31ref()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::ref_is_null(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck RefEq" {
  let eq_ref = ValType::ref_type(
    RefType::new(true, HeapType::abs(AbsHeapType::eq())),
  )
  let state = make_state(empty_env(), [eq_ref, eq_ref])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::ref_eq(), state))
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck RefAsNonNull" {
  let state = make_state(empty_env(), [nullable_funcref()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::ref_as_non_null(), state),
  )
  assert_stack(new_state, [non_nullable_funcref()])
}

///|
test "Typecheck RefAsNonNull accepts abstract nullable externref" {
  let externref = ValType::ref_type(RefType::abs(AbsHeapType::extern_()))
  let state = make_state(empty_env(), [externref])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::ref_as_non_null(), state),
  )
  assert_stack(new_state, [non_nullable_externref()])
}

///|
test "Typecheck RefAsNonNull accepts unreachable and non-null operands" {
  let unreachable_state = assert_ok(
    Typecheck::typecheck(
      Instruction::unreachable_(),
      make_state(empty_env(), []),
    ),
  )
  let after_unreachable = assert_ok(
    Typecheck::typecheck(Instruction::ref_as_non_null(), unreachable_state),
  )
  assert_stack(after_unreachable, [ValType::bottom()])

  let non_nullable_anyref = ValType::ref_type(
    RefType::new(false, HeapType::abs(AbsHeapType::any())),
  )
  let non_null_state = make_state(empty_env(), [non_nullable_anyref])
  let after_non_null = assert_ok(
    Typecheck::typecheck(Instruction::ref_as_non_null(), non_null_state),
  )
  assert_stack(after_non_null, [non_nullable_anyref])
}

// BrOnNonNull tests

///|
test "Typecheck BrOnNonNull basic" {
  let env = empty_env().with_labels([[non_nullable_funcref()]])
  let state = make_state(env, [nullable_funcref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_non_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  assert_stack(new_state, [])
}

///|
test "Typecheck BrOnNonNull with prefix types" {
  let env = empty_env().with_labels([[ValType::i32(), non_nullable_funcref()]])
  let state = make_state(env, [ValType::i32(), nullable_funcref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_non_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck BrOnNonNull with externref" {
  let env = empty_env().with_labels([[non_nullable_externref()]])
  let state = make_state(env, [nullable_externref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_non_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  assert_stack(new_state, [])
}

///|
test "Typecheck BrOnNonNull accepts statically non-null ref operand" {
  let env = empty_env().with_labels([[@lib.ValType::i31ref()]])
  let state = make_state(env, [@lib.ValType::i31ref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_non_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  assert_stack(new_state, [])
}

///|
test "Typecheck BrOnNonNull accepts unreachable polymorphic operand" {
  let env = empty_env().with_labels([[non_nullable_externref()]])
  let unreachable_state = assert_ok(
    Typecheck::typecheck(Instruction::unreachable_(), make_state(env, [])),
  )
  let result = Typecheck::typecheck(
    Instruction::br_on_non_null(LabelIdx::new(0)),
    unreachable_state,
  )
  let new_state = assert_ok(result)
  @debug.assert_eq(new_state.reachable, false)
  assert_stack(new_state, [])
}

///|
test "Typecheck BrOnNonNull rejects empty label payloads" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [nullable_funcref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_non_null(LabelIdx::new(0)),
    state,
  )
  match result {
    Ok(_) => fail("expected empty br_on_non_null label payloads to fail")
    Err(msg) => @debug.assert_eq(msg, "br_on_non_null label type too short")
  }
}

///|
test "Typecheck BrOnNonNull fails with wrong label type" {
  let env = empty_env().with_labels([[ValType::i32()]])
  let state = make_state(env, [nullable_funcref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_non_null(LabelIdx::new(0)),
    state,
  )
  assert_true(result is Err(_))
}

// BrOnNull tests

///|
test "Typecheck BrOnNull basic" {
  let env = empty_env().with_labels([[]]) // label takes no values
  let state = make_state(env, [nullable_funcref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  // Fallthrough pushes non-nullable ref
  assert_stack(new_state, [non_nullable_funcref()])
}

///|
test "Typecheck BrOnNull accepts statically non-null ref operand" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [@lib.ValType::i31ref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  assert_stack(new_state, [@lib.ValType::i31ref()])
}

///|
test "Typecheck BrOnNull with prefix types" {
  let env = empty_env().with_labels([[ValType::i32()]])
  let state = make_state(env, [ValType::i32(), nullable_funcref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  assert_stack(new_state, [ValType::i32(), non_nullable_funcref()])
}

///|
test "Typecheck BrOnNull with externref" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [nullable_externref()])
  let result = Typecheck::typecheck(
    Instruction::br_on_null(LabelIdx::new(0)),
    state,
  )
  let new_state = assert_ok(result)
  assert_stack(new_state, [non_nullable_externref()])
}

///|
test "Typecheck RefI31" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(Typecheck::typecheck(Instruction::ref_i31(), state))
  @debug.assert_eq(new_state.stack.length(), 1)
}

///|
test "Typecheck I31GetS" {
  let state = make_state(empty_env(), [i31ref_type()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i31_get_s(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I31GetU" {
  let state = make_state(empty_env(), [i31ref_type()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i31_get_u(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck AnyConvertExtern" {
  let state = make_state(empty_env(), [nullable_externref()])
  let result = Typecheck::typecheck(Instruction::any_convert_extern(), state)
  let new_state = assert_ok(result)
  assert_stack(new_state, [ValType::anyref()])
}

///|
test "Typecheck AnyConvertExtern preserves non-null operands" {
  let state = make_state(empty_env(), [non_nullable_externref()])
  let result = Typecheck::typecheck(Instruction::any_convert_extern(), state)
  let new_state = assert_ok(result)
  assert_stack(new_state, [
    ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::any()))),
  ])
}

///|
test "Typecheck ExternConvertAny" {
  let any_ref = ValType::ref_type(
    RefType::new(true, HeapType::abs(AbsHeapType::any())),
  )
  let state = make_state(empty_env(), [any_ref])
  let result = Typecheck::typecheck(Instruction::extern_convert_any(), state)
  let new_state = assert_ok(result)
  assert_stack(new_state, [ValType::externref()])
}

///|
test "Typecheck ExternConvertAny preserves non-null operands" {
  let any_ref = ValType::ref_type(
    RefType::new(false, HeapType::abs(AbsHeapType::any())),
  )
  let state = make_state(empty_env(), [any_ref])
  let result = Typecheck::typecheck(Instruction::extern_convert_any(), state)
  let new_state = assert_ok(result)
  assert_stack(new_state, [non_nullable_externref()])
}

// =============================================================================
// VECTOR (SIMD) INSTRUCTIONS
// =============================================================================

///|
test "Typecheck I32Const pushes i32" {
  let env = empty_env()
  let state = make_state_owned(env, [])
  let result = Typecheck::typecheck(Instruction::i32_const(I32(42)), state)
  match result {
    Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i32()])
    Err(e) => fail("I32Const should typecheck: \{e}")
  }
}

///|
test "Typecheck I32Add consumes two i32s and produces one" {
  let env = empty_env()
  let state = make_state_owned(env, [ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i32_add(), state)
  match result {
    Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i32()])
    Err(e) => fail("I32Add should typecheck: \{e}")
  }
}

///|
test "Typecheck I32Add fails with insufficient stack" {
  let env = empty_env()
  let state = make_state_owned(env, [])
  let result = Typecheck::typecheck(Instruction::i32_add(), state)
  match result {
    Ok(_) => fail("I32Add should fail with empty stack")
    Err(_) => ()
  }
}

///|
test "Typecheck Drop removes top of stack" {
  let env = empty_env()
  let state = make_state_owned(env, [ValType::i32()])
  let result = Typecheck::typecheck(Instruction::drop(), state)
  match result {
    Ok(new_state) => @debug.assert_eq(new_state.stack, [])
    Err(e) => fail("Drop should typecheck: \{e}")
  }
}

///|
test "Typecheck Unreachable makes state unreachable" {
  let env = empty_env()
  let state = make_state_owned(env, [])
  let result = Typecheck::typecheck(Instruction::unreachable_(), state)
  match result {
    Ok(new_state) => @debug.assert_eq(new_state.reachable, false)
    Err(e) => fail("Unreachable should typecheck: \{e}")
  }
}

///|
test "Typecheck empty Expr" {
  let env = empty_env()
  let state = make_state_owned(env, [])
  let expr = Expr::new([])
  let result = Typecheck::typecheck(expr, state)
  match result {
    Ok(new_state) => @debug.assert_eq(new_state.stack, [])
    Err(e) => fail("Empty Expr should typecheck: \{e}")
  }
}

///|
test "Typecheck Expr sequence" {
  let env = empty_env()
  let state = make_state_owned(env, [])
  let expr = Expr::new([
    Instruction::i32_const(I32(1)),
    Instruction::i32_const(I32(2)),
    Instruction::i32_add(),
  ])
  let result = Typecheck::typecheck(expr, state)
  match result {
    Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i32()])
    Err(e) => fail("Expr sequence should typecheck: \{e}")
  }
}

///|
test "Typecheck I64Const pushes i64" {
  let env = empty_env()
  let state = make_state_owned(env, [])
  let result = Typecheck::typecheck(Instruction::i64_const(I64(100L)), state)
  match result {
    Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i64()])
    Err(e) => fail("I64Const should typecheck: \{e}")
  }
}

///|
test "Typecheck type mismatch fails" {
  let env = empty_env()
  let state = make_state_owned(env, [ValType::i64(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i32_add(), state)
  match result {
    Ok(_) => fail("I32Add should fail with mismatched types")
    Err(_) => ()
  }
}

///|
test "Expr typecheck errors include instruction and stack context" {
  let expression = Expr::new([
    Instruction::i64_const(I64(1L)),
    Instruction::i32_const(I32(2)),
    Instruction::i32_add(),
  ])
  match Typecheck::typecheck(expression, make_state_owned(empty_env(), [])) {
    Ok(_) => fail("expected mixed scalar add to fail")
    Err(error) => {
      assert_true(error.contains("instruction[2]"))
      assert_true(error.contains("stack="))
      assert_true(error.contains("type mismatch"))
    }
  }
}

///|
test "Typecheck V128Const" {
  let state = make_state(empty_env(), [])
  let instr = Instruction::v128_const(
    b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08',
    b'\x09', b'\x0a', b'\x0b', b'\x0c', b'\x0d', b'\x0e', b'\x0f',
  )
  let new_state = assert_ok(Typecheck::typecheck(instr, state))
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck V128Load" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32()])
  let arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::v128_load(arg), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck V128Store" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::v128_store(arg), state),
  )
  assert_stack(new_state, [])
}

///|
test "Typecheck I8x16Splat" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i8x16_splat(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I16x8Splat" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i16x8_splat(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I32x4Splat" {
  let state = make_state(empty_env(), [ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32x4_splat(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I64x2Splat" {
  let state = make_state(empty_env(), [ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i64x2_splat(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Splat" {
  let state = make_state(empty_env(), [ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32x4_splat(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Splat" {
  let state = make_state(empty_env(), [ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f64x2_splat(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I8x16ExtractLaneS" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i8x16_extract_lane_s(LaneIdx::new(0)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I16x8ExtractLaneU" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i16x8_extract_lane_u(LaneIdx::new(3)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I32x4ExtractLane" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i32x4_extract_lane(LaneIdx::new(1)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck I64x2ExtractLane" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i64x2_extract_lane(LaneIdx::new(0)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::i64()])
}

///|
test "Typecheck F32x4ExtractLane" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::f32x4_extract_lane(LaneIdx::new(2)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::f32()])
}

///|
test "Typecheck F64x2ExtractLane" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::f64x2_extract_lane(LaneIdx::new(1)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::f64()])
}

///|
test "Typecheck I8x16ReplaceLane" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i8x16_replace_lane(LaneIdx::new(0)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I16x8ReplaceLane" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i16x8_replace_lane(LaneIdx::new(2)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I32x4ReplaceLane" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::i32()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i32x4_replace_lane(LaneIdx::new(1)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I64x2ReplaceLane" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::i64()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::i64x2_replace_lane(LaneIdx::new(0)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck F32x4ReplaceLane" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::f32()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::f32x4_replace_lane(LaneIdx::new(3)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck F64x2ReplaceLane" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::f64()])
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::f64x2_replace_lane(LaneIdx::new(1)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck V128Not" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::v128_not(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I8x16Abs" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i8x16_abs(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I16x8Neg" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i16x8_neg(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I32x4AllTrue" {
  let state = make_state(empty_env(), [ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32x4_all_true(), state),
  )
  assert_stack(new_state, [ValType::i32()])
}

///|
test "Typecheck V128And" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::v128_and(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I8x16Add" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i8x16_add(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I16x8Mul" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i16x8_mul(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I32x4Sub" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32x4_sub(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Div" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32x4_div(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I8x16Eq" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i8x16_eq(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I16x8LtS" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i16x8_lt_s(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck I32x4GtU" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::i32x4_gt_u(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Le" {
  let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::f32x4_le(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck V128Bitselect" {
  let state = make_state(empty_env(), [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let new_state = assert_ok(
    Typecheck::typecheck(Instruction::v128_bitselect(), state),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck V128Load8Lane" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::v128_load8_lane(arg, LaneIdx::new(0)),
      state,
    ),
  )
  assert_stack(new_state, [ValType::v128()])
}

///|
test "Typecheck V128Store16Lane" {
  let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let arg = MemArg::new(U32(0), None, U64(0))
  let new_state = assert_ok(
    Typecheck::typecheck(
      Instruction::v128_store16_lane(arg, LaneIdx::new(2)),
      state,
    ),
  )
  assert_stack(new_state, [])
}

// =============================================================================
// CALL INSTRUCTIONS
// =============================================================================

///|
test "Typecheck Call" {
  let func_type = FuncType::new([ValType::i32()], [ValType::i64()])
  let env = empty_env().push_func(func_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck Call no args no results" {
  let func_type = FuncType::new([], [])
  let env = empty_env().push_func(func_type)
  let state = make_state(env, [])
  let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck Call invalid func index" {
  let env = empty_env()
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
  assert_err(result)
}

///|
test "Typecheck Call type mismatch" {
  let func_type = FuncType::new([ValType::i32()], [])
  let env = empty_env().push_func(func_type)
  let state = make_state(env, [ValType::i64()])
  let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
  assert_err(result)
}

///|
test "Typecheck CallIndirect" {
  let func_subtype = SubType::comp_type(
    CompType::func([ValType::i32()], [ValType::i64()]),
  )
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(1, None),
  )
  let env = { ..empty_env(), global_types: [func_subtype], }.push_table(
    table_type,
  )
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::call_indirect(TypeIdx::new(0), TableIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck ReturnCall" {
  let func_type = FuncType::new([ValType::i32()], [ValType::i64()])
  let env = empty_env()
    .push_func(func_type)
    .with_return_type(Some([ValType::i64()]))
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::return_call(FuncIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.reachable, false)
}

///|
test "Typecheck ReturnCallIndirect" {
  let func_subtype = SubType::comp_type(
    CompType::func([ValType::i32()], [ValType::i64()]),
  )
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(1, None),
  )
  let env = { ..empty_env(), global_types: [func_subtype], }
    .push_table(table_type)
    .with_return_type(Some([ValType::i64()]))
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::return_call_indirect(TypeIdx::new(0), TableIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.reachable, false)
}

///|
test "Typecheck CallRef" {
  let func_subtype = SubType::comp_type(
    CompType::func([ValType::i32()], [ValType::i64()]),
  )
  let env = { ..empty_env(), global_types: [func_subtype], }
  let ref_type = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [ValType::i32(), ref_type])
  let result = Typecheck::typecheck(
    Instruction::call_ref(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck ReturnCallRef" {
  let func_subtype = SubType::comp_type(
    CompType::func([ValType::i32()], [ValType::i64()]),
  )
  let env = { ..empty_env(), global_types: [func_subtype], }.with_return_type(
    Some([ValType::i64()]),
  )
  let ref_type = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [ValType::i32(), ref_type])
  let result = Typecheck::typecheck(
    Instruction::return_call_ref(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.reachable, false)
}

// =============================================================================
// REFERENCE INSTRUCTIONS
// =============================================================================

///|
test "Typecheck RefFunc" {
  let func_type = FuncType::new([], [])
  let env = empty_env().push_func(func_type)
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::ref_func(FuncIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.stack.length(), 1)
}

///|
test "Typecheck RefFunc infers concrete function heap type when unique" {
  let ft = FuncType::new([], [])
  let st = SubType::comp_type(CompType::func([], []))
  let env = empty_env().with_types([st]).push_func(ft)
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::ref_func(FuncIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
    ),
  ])
}

///|
test "Typecheck RefFunc prefers declared function type index when available" {
  let ft = FuncType::new([], [])
  let st0 = SubType::comp_type(CompType::func([], []))
  let st1 = SubType::comp_type(CompType::func([], []))
  let env = empty_env()
    .with_types([st0, st1])
    .push_func_with_typeidx(ft, Some(TypeIdx::new(1)))
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::ref_func(FuncIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(1)), exact=true),
    ),
  ])
}

///|
test "Typecheck RefFunc invalid index" {
  let env = empty_env()
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::ref_func(FuncIdx::new(0)),
    state,
  )
  assert_err(result)
}

///|
test "Typecheck RefTest non-nullable" {
  let env = empty_env()
  let state = make_state(env, [anyref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test(false, HeapType::abs(AbsHeapType::i31())),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck RefTest eq from any" {
  let env = empty_env()
  let state = make_state(env, [anyref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test(true, HeapType::abs(AbsHeapType::eq())),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck RefTest struct from eq" {
  let env = empty_env()
  let state = make_state(env, [eqref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test(true, HeapType::abs(AbsHeapType::struct_())),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck RefTest rejects operands from a distinct reference hierarchy" {
  let env = empty_env()
  // i31ref on stack, testing for funcref - these are incompatible hierarchies
  let state = make_state(env, [i31ref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test(true, HeapType::abs(AbsHeapType::func())),
    state,
  )
  assert_true(result is Err(_))
}

///|
test "Typecheck RefTest with concrete type recursive" {
  let struct_subtype = SubType::comp_type(CompType::struct_([]))
  let env = empty_env().with_rectype(RecType::new(struct_subtype))
  let state = make_state(env, [anyref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test(true, HeapType::new(TypeIdx::rec(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck RefTest with concrete type in global types" {
  let struct_subtype = SubType::comp_type(CompType::struct_([]))
  let env : Env = { ..empty_env(), global_types: [struct_subtype], }
  let state = make_state(env, [anyref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test(true, HeapType::new(TypeIdx::new(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck exact RefTest accepts exact concrete source" {
  let struct_subtype = SubType::comp_type(CompType::struct_([]))
  let env : Env = { ..empty_env(), global_types: [struct_subtype], }
  let exact_struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
  )
  let state = make_state(env, [exact_struct_ref])
  let result = Typecheck::typecheck(
    Instruction::ref_test_exact(false, HeapType::new(TypeIdx::new(0U))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck exact RefTest rejects abstract exact target" {
  let env = empty_env()
  let state = make_state(env, [anyref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test_exact(false, HeapType::abs(AbsHeapType::eq())),
    state,
  )
  assert_err(result)
}

///|
test "Typecheck RefCast non-nullable" {
  let env = empty_env()
  let state = make_state(env, [anyref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_cast(false, HeapType::abs(AbsHeapType::i31())),
    state,
  )
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.stack.length(), 1)
}

///|
test "Typecheck RefCast accepts exact source for inexact same heap target" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::funcref()), Var),
    ]),
  )
  let env : Env = { ..empty_env(), global_types: [struct_type], }
  let exact_struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
  )
  let state = make_state(env, [exact_struct_ref])
  let result = Typecheck::typecheck(
    Instruction::ref_cast(false, HeapType::new(TypeIdx::new(0U))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
  ])
}

///|
test "Typecheck RefGetDesc stack effect" {
  let env = typecheck_descriptor_struct_env()
  let state = make_state(env, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
    ),
  ])
  let result = Typecheck::typecheck(
    Instruction::ref_get_desc(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(1)), exact=true),
    ),
  ])
}

///|
test "Typecheck RefTestDesc stack effect" {
  let env = empty_env()
  let state = make_state(env, [anyref_type()])
  let result = Typecheck::typecheck(
    Instruction::ref_test_desc(true, HeapType::abs(AbsHeapType::eq())),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck RefCastDescEq stack effect" {
  let described = SubType::comp_type(
    CompType::struct_([]),
    metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(1U))),
  )
  let descriptor = SubType::comp_type(
    CompType::struct_([]),
    metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
  )
  let env = Env::{ ..empty_env(), global_types: [described, descriptor], }
  let state = make_state(env, [
    anyref_type(),
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(1U)))),
  ])
  let result = Typecheck::typecheck(
    Instruction::ref_cast_desc_eq(true, HeapType::new(TypeIdx::new(0U))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::ref_null(HeapType::new(TypeIdx::new(0U)))])
}

///|
test "Typecheck exact RefCastDescEq accepts inexact descriptor operand" {
  let env = typecheck_descriptor_struct_env()
  let state = make_state(env, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
    ),
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(1U)))),
  ])
  let result = Typecheck::typecheck(
    Instruction::ref_cast_desc_eq_type(
      RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
    ),
  ])
}

///|
test "Typecheck exact RefCastDescEq accepts nullable strict-subtype operands" {
  // OI-J descriptor exactness validation: Binaryen and wasm-tools accept a
  // nullable strict-subtype reference operand for an exact nullable supertype
  // descriptor cast. Non-null subtype values may trap at runtime, while null can
  // still pass the nullable target, so final validation must not reject the
  // Binaryen-shaped exactified cast.
  let base = SubType::new(
    false,
    [],
    CompType::struct_([]),
    metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(1U))),
  )
  let base_descriptor = SubType::new(
    false,
    [],
    CompType::struct_([]),
    metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
  )
  let subtype = SubType::new(
    false,
    [TypeIdx::new(0U)],
    CompType::struct_([]),
    metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(3U))),
  )
  let subtype_descriptor = SubType::new(
    false,
    [TypeIdx::new(1U)],
    CompType::struct_([]),
    metadata=TypeMetadata::new(describes=Some(TypeIdx::new(2U))),
  )
  let env = Env::{
    ..empty_env(),
    global_types: [base, base_descriptor, subtype, subtype_descriptor],
  }
  let state = make_state(env, [
    ValType::ref_type(RefType::new(true, HeapType::new(TypeIdx::new(2U)))),
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(1U)), exact=true),
    ),
  ])
  let result = Typecheck::typecheck(
    Instruction::ref_cast_desc_eq_type(
      RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
    ),
  ])
}

///|
test "Typecheck RefCastDescEq rejects mismatched descriptor describes target" {
  let described = SubType::comp_type(
    CompType::struct_([]),
    metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(2U))),
  )
  let other = SubType::comp_type(CompType::struct_([]))
  let descriptor = SubType::comp_type(
    CompType::struct_([]),
    metadata=TypeMetadata::new(describes=Some(TypeIdx::new(1U))),
  )
  let env = Env::{
    ..empty_env(),
    global_types: [described, other, descriptor],
  }
  let state = make_state(env, [
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(2U)))),
  ])
  let result = Typecheck::typecheck(
    Instruction::ref_cast_desc_eq(false, HeapType::new(TypeIdx::new(0U))),
    state,
  )
  assert_err(result)
}

///|
test "Typecheck RefCastDescEq rejects nullable descriptor operand" {
  let env = typecheck_descriptor_struct_env()
  let state = make_state(env, [
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
    ValType::ref_type(RefType::new(true, HeapType::new(TypeIdx::new(1U)))),
  ])
  let result = Typecheck::typecheck(
    Instruction::ref_cast_desc_eq(false, HeapType::new(TypeIdx::new(0U))),
    state,
  )
  assert_err(result)
}

///|
test "Typecheck RefCastDescEq rejects descriptorless target" {
  let target = SubType::comp_type(CompType::struct_([]))
  let descriptor = SubType::comp_type(
    CompType::struct_([]),
    metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
  )
  let env = Env::{ ..empty_env(), global_types: [target, descriptor], }
  let state = make_state(env, [
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
    ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(1U)))),
  ])
  let result = Typecheck::typecheck(
    Instruction::ref_cast_desc_eq(false, HeapType::new(TypeIdx::new(0U))),
    state,
  )
  assert_err(result)
}

///|
test "Typecheck BrOnCast" {
  let nullable_i31 = ValType::ref_type(
    RefType::new(true, HeapType::abs(AbsHeapType::i31())),
  )
  let env = empty_env().with_labels([[nullable_i31]])
  let state = make_state(env, [anyref_type()])
  let cast_op = CastOp::new(true, true)
  let t = Instruction::br_on_cast(
    LabelIdx::new(0),
    cast_op.0,
    HeapType::abs(AbsHeapType::any()),
    cast_op.1,
    HeapType::abs(AbsHeapType::i31()),
  )
  let result = Typecheck::typecheck(t, state)
  ignore(assert_ok(result))
}

///|
test "Typecheck BrOnCast rejects empty label payloads" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [anyref_type()])
  let cast_op = CastOp::new(true, true)
  let t = Instruction::br_on_cast(
    LabelIdx::new(0),
    cast_op.0,
    HeapType::abs(AbsHeapType::any()),
    cast_op.1,
    HeapType::abs(AbsHeapType::i31()),
  )
  let result = Typecheck::typecheck(t, state)
  match result {
    Ok(_) => fail("expected empty br_on_cast label payloads to fail")
    Err(msg) => @debug.assert_eq(msg, "br_on_cast label type too short")
  }
}

///|
test "Typecheck BrOnCastFail" {
  let env = empty_env().with_labels([[anyref_type()]])
  let state = make_state(env, [anyref_type()])
  let cast_op = CastOp::new(true, true)
  let t = Instruction::br_on_cast_fail(
    LabelIdx::new(0),
    cast_op.0,
    HeapType::abs(AbsHeapType::any()),
    cast_op.1,
    HeapType::abs(AbsHeapType::i31()),
  )
  let result = Typecheck::typecheck(t, state)
  ignore(assert_ok(result))
}

///|
test "Typecheck BrOnCastFail rejects empty label payloads" {
  let env = empty_env().with_labels([[]])
  let state = make_state(env, [anyref_type()])
  let cast_op = CastOp::new(true, true)
  let t = Instruction::br_on_cast_fail(
    LabelIdx::new(0),
    cast_op.0,
    HeapType::abs(AbsHeapType::any()),
    cast_op.1,
    HeapType::abs(AbsHeapType::i31()),
  )
  let result = Typecheck::typecheck(t, state)
  match result {
    Ok(_) => fail("expected empty br_on_cast_fail label payloads to fail")
    Err(msg) => @debug.assert_eq(msg, "br_on_cast_fail label type too short")
  }
}

// =============================================================================
// EXCEPTION HANDLING INSTRUCTIONS
// =============================================================================

///|
test "Typecheck Throw" {
  let tag_subtype = SubType::comp_type(CompType::func([ValType::i32()], []))
  let env = { ..empty_env(), global_types: [tag_subtype], }.push_tag(
    TagType::new(TypeIdx::new(0)),
  )
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(Instruction::throw_(TagIdx::new(0)), state)
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.reachable, false)
}

///|
test "Typecheck Throw invalid tag" {
  let env = empty_env()
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(Instruction::throw_(TagIdx::new(0)), state)
  assert_err(result)
}

///|
test "Typecheck ThrowRef" {
  let exnref = ValType::ref_type(
    RefType::new(false, HeapType::abs(AbsHeapType::exn())),
  )
  let env = empty_env()
  let state = make_state(env, [exnref])
  let result = Typecheck::typecheck(Instruction::throw_ref(), state)
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.reachable, false)
}

///|
test "Typecheck TryTable empty" {
  let env = empty_env()
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::try_table(BlockType::void_(), [], Expr::new([])),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck TryTable with result" {
  let env = empty_env()
  let state = make_state(env, [])
  let block_type = BlockType::val_type(ValType::i32())
  let result = Typecheck::typecheck(
    Instruction::try_table(
      block_type,
      [],
      Expr::new([Instruction::i32_const(I32(42))]),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck TryTable with param input" {
  let env = empty_env().with_rectype(
    RecType::new(SubType::comp_type(CompType::func([ValType::i32()], []))),
  )
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::try_table(
      BlockType::type_idx(TypeIdx::new(0)),
      [],
      Expr::new([Instruction::drop()]),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck TryTable catch rejects invalid tag index" {
  let tag_sig = SubType::comp_type(CompType::func([], []))
  let env = { ..empty_env(), global_types: [tag_sig], }.push_tag(
    TagType::new(TypeIdx::new(0)),
  )
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::try_table(
      BlockType::void_(),
      [Catch::new(TagIdx::new(1), LabelIdx::new(0))],
      Expr::new([Instruction::nop()]),
    ),
    state,
  )
  assert_err(result)
}

///|
test "Typecheck TryTable catch_ref rejects invalid tag index" {
  let tag_sig = SubType::comp_type(CompType::func([], []))
  let env = { ..empty_env(), global_types: [tag_sig], }.push_tag(
    TagType::new(TypeIdx::new(0)),
  )
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::try_table(
      BlockType::void_(),
      [Catch::ref_(TagIdx::new(1), LabelIdx::new(0))],
      Expr::new([Instruction::nop()]),
    ),
    state,
  )
  assert_err(result)
}

///|
test "Typecheck TryTable catch label 0 uses outer depth semantics" {
  let tag_sig = SubType::comp_type(CompType::func([ValType::i64()], []))
  let env = { ..empty_env(), global_types: [tag_sig], }
    .push_tag(TagType::new(TypeIdx::new(0)))
    .with_labels([[ValType::i64()]])
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::try_table(
      BlockType::val_type(ValType::i32()),
      [Catch::new(TagIdx::new(0), LabelIdx::new(0))],
      Expr::new([Instruction::i32_const(I32(7))]),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck TryTable catch label 1 uses outer depth semantics" {
  let tag_sig = SubType::comp_type(CompType::func([ValType::i64()], []))
  let env = { ..empty_env(), global_types: [tag_sig], }
    .push_tag(TagType::new(TypeIdx::new(0)))
    .with_labels([[ValType::i64()], [ValType::i32()]])
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::try_table(
      BlockType::val_type(ValType::i32()),
      [Catch::new(TagIdx::new(0), LabelIdx::new(1))],
      Expr::new([Instruction::i32_const(I32(9))]),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

// =============================================================================
// STRUCT INSTRUCTIONS
// =============================================================================

///|
fn typecheck_descriptor_struct_env() -> Env {
  let described = SubType::comp_type(
    CompType::struct_([]),
    metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(1U))),
  )
  let descriptor = SubType::comp_type(
    CompType::struct_([]),
    metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
  )
  { ..empty_env(), global_types: [described, descriptor], }
}

///|
test "Typecheck StructNew" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::i32()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::struct_new(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.stack.length(), 1)
}

///|
test "Typecheck StructNewDefault" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::i32()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::struct_new_default(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  @debug.assert_eq(final_state.stack.length(), 1)
}

///|
test "Typecheck StructNewDesc" {
  let env = typecheck_descriptor_struct_env()
  let state = make_state(env, [
    ValType::ref_type(
      RefType::new(true, HeapType::new(TypeIdx::new(1U)), exact=true),
    ),
  ])
  let result = Typecheck::typecheck(
    Instruction::struct_new_default_desc(TypeIdx::new(0U)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
    ),
  ])
}

///|
test "Typecheck StructGet" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::i32()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref])
  let result = Typecheck::typecheck(
    Instruction::struct_get(TypeIdx::new(0), U32(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck StructGetS" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::pack_type(PackType::i8()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref])
  let result = Typecheck::typecheck(
    Instruction::struct_get_s(TypeIdx::new(0), U32(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck StructGetU" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::pack_type(PackType::i16()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref])
  let result = Typecheck::typecheck(
    Instruction::struct_get_u(TypeIdx::new(0), U32(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck StructAtomicGet" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::i64()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref])
  let result = Typecheck::typecheck(
    Instruction::struct_atomic_get(
      AtomicOrder::acq_rel(),
      TypeIdx::new(0),
      U32(0),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck StructAtomicGetS" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::pack_type(PackType::i8()), Const),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref])
  let result = Typecheck::typecheck(
    Instruction::struct_atomic_get_s(
      AtomicOrder::seq_cst(),
      TypeIdx::new(0),
      U32(0),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck StructAtomicGetU" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::pack_type(PackType::i16()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref])
  let result = Typecheck::typecheck(
    Instruction::struct_atomic_get_u(
      AtomicOrder::acq_rel(),
      TypeIdx::new(0),
      U32(0),
    ),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck StructAtomicGet rejects packed field without signedness" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::pack_type(PackType::i8()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref])
  match
    Typecheck::typecheck(
      Instruction::struct_atomic_get(
        AtomicOrder::acq_rel(),
        TypeIdx::new(0),
        U32(0),
      ),
      state,
    ) {
    Ok(_) => fail("expected struct.atomic.get packed-field rejection")
    Err(message) => assert_true(message.contains("packed field"))
  }
}

///|
test "Typecheck StructAtomicRmw and cmpxchg" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::i32()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let rmw_state = make_state(env, [struct_ref, ValType::i32()])
  let rmw = assert_ok(
    Typecheck::typecheck(
      Instruction::struct_atomic_rmw(
        @lib.AggregateAtomicRmwOp::add(),
        TypeIdx::new(0),
        U32(0),
      ),
      rmw_state,
    ),
  )
  assert_stack(rmw, [ValType::i32()])
  let cmp_state = make_state(env, [struct_ref, ValType::i32(), ValType::i32()])
  let cmp = assert_ok(
    Typecheck::typecheck(
      Instruction::struct_atomic_cmpxchg(TypeIdx::new(0), U32(0)),
      cmp_state,
    ),
  )
  assert_stack(cmp, [ValType::i32()])
}

///|
test "Typecheck StructAtomicRmw rejects invalid field shapes" {
  let packed_struct = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::pack_type(PackType::i8()), Var),
    ]),
  )
  let immutable_struct = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::i32()), Const),
    ]),
  )
  let env = { ..empty_env(), global_types: [packed_struct, immutable_struct], }
  let ref0 = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let ref1 = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(1))),
  )
  match
    Typecheck::typecheck(
      Instruction::struct_atomic_rmw(
        @lib.AggregateAtomicRmwOp::add(),
        TypeIdx::new(0),
        U32(0),
      ),
      make_state(env, [ref0, ValType::i32()]),
    ) {
    Ok(_) => fail("expected packed aggregate atomic rejection")
    Err(message) => assert_true(message.contains("non-packed"), msg=message)
  }
  match
    Typecheck::typecheck(
      Instruction::struct_atomic_rmw(
        @lib.AggregateAtomicRmwOp::add(),
        TypeIdx::new(1),
        U32(0),
      ),
      make_state(env, [ref1, ValType::i32()]),
    ) {
    Ok(_) => fail("expected immutable aggregate atomic rejection")
    Err(message) => assert_true(message.contains("immutable"), msg=message)
  }
  match
    Typecheck::typecheck(
      Instruction::struct_atomic_rmw(
        @lib.AggregateAtomicRmwOp::add(),
        TypeIdx::new(0),
        U32(3),
      ),
      make_state(env, [ref0, ValType::i32()]),
    ) {
    Ok(_) => fail("expected invalid field index rejection")
    Err(message) => assert_true(message.contains("out of range"), msg=message)
  }
}

///|
test "Typecheck StructSet" {
  let struct_type = SubType::comp_type(
    CompType::struct_([
      FieldType::new(StorageType::val_type(ValType::i32()), Var),
    ]),
  )
  let env = { ..empty_env(), global_types: [struct_type], }
  let struct_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [struct_ref, ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::struct_set(TypeIdx::new(0), U32(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

// =============================================================================
// ARRAY INSTRUCTIONS
// =============================================================================

///|
test "Typecheck ArrayNew" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_new(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
    ),
  ])
}

///|
test "Typecheck ArrayNewDefault" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_new_default(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
    ),
  ])
}

///|
test "Typecheck ArrayNewFixed" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_new_fixed(TypeIdx::new(0), U32(3)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
    ),
  ])
}

///|
test "Typecheck ArrayNewData" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let data = Data::new(DataMode::passive(), b"")
  let env = { ..empty_env(), global_types: [array_type], }.push_data(data)
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_new_data(TypeIdx::new(0), DataIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
    ),
  ])
}

///|
test "Typecheck ArrayNewElem" {
  let funcref = RefType::abs(AbsHeapType::func())
  let array_type = SubType::comp_type(
    CompType::array(
      FieldType::new(StorageType::val_type(ValType::ref_type(funcref)), Var),
    ),
  )
  let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
  let env = { ..empty_env(), global_types: [array_type], }.push_elem(elem)
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_new_elem(TypeIdx::new(0), ElemIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [
    ValType::ref_type(
      RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
    ),
  ])
}

///|
test "Typecheck StringNewWtf16Array" {
  let array_type = SubType::comp_type(
    CompType::array(
      FieldType::new(StorageType::pack_type(PackType::i16()), Const),
    ),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let state = make_state(env, [
    ValType::ref_array_nullable(TypeIdx::new(0)),
    ValType::i32(),
    ValType::i32(),
  ])
  let result = Typecheck::typecheck(
    Instruction::string_new_wtf16_array(),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [typecheck_nonnull_string_type()])
}

///|
test "Typecheck StringConst" {
  let state = make_state(empty_env(), [])
  let result = Typecheck::typecheck(Instruction::string_const(b"hello"), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [typecheck_nonnull_string_type()])
}

///|
test "Typecheck StringEncodeUtf8Array" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::pack_type(PackType::i8()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let state = make_state(env, [
    ValType::stringref(),
    ValType::ref_array_nullable(TypeIdx::new(0)),
    ValType::i32(),
  ])
  let result = Typecheck::typecheck(
    Instruction::string_encode_utf8_array(),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck StringEncodeWtf16Array rejects immutable arrays" {
  let array_type = SubType::comp_type(
    CompType::array(
      FieldType::new(StorageType::pack_type(PackType::i16()), Const),
    ),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let state = make_state(env, [
    ValType::stringref(),
    ValType::ref_array_nullable(TypeIdx::new(0)),
    ValType::i32(),
  ])
  match Typecheck::typecheck(Instruction::string_encode_wtf16_array(), state) {
    Err(err) => assert_true(err.contains("mutable"))
    Ok(_) => fail("expected immutable string.encode_wtf16_array array to fail")
  }
}

///|
test "Typecheck ArrayGet" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [array_ref, ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_get(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck ArrayGetS" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::pack_type(PackType::i8()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [array_ref, ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_get_s(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck ArrayGetU" {
  let array_type = SubType::comp_type(
    CompType::array(
      FieldType::new(StorageType::pack_type(PackType::i16()), Var),
    ),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [array_ref, ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_get_u(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck ArraySet" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [array_ref, ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::array_set(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck ArrayLen" {
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::abs(AbsHeapType::array())),
  )
  let env = empty_env()
  let state = make_state(env, [array_ref])
  let result = Typecheck::typecheck(Instruction::array_len(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck ArrayFill" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [
    array_ref,
    ValType::i32(),
    ValType::i32(),
    ValType::i32(),
  ])
  let result = Typecheck::typecheck(
    Instruction::array_fill(TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck ArrayCopy" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let env = { ..empty_env(), global_types: [array_type], }
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [
    array_ref,
    ValType::i32(),
    array_ref,
    ValType::i32(),
    ValType::i32(),
  ])
  let result = Typecheck::typecheck(
    Instruction::array_copy(TypeIdx::new(0), TypeIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck ArrayInitData" {
  let array_type = SubType::comp_type(
    CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
  )
  let data = Data::new(DataMode::passive(), b"")
  let env = { ..empty_env(), global_types: [array_type], }.push_data(data)
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [
    array_ref,
    ValType::i32(),
    ValType::i32(),
    ValType::i32(),
  ])
  let result = Typecheck::typecheck(
    Instruction::array_init_data(TypeIdx::new(0), DataIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck ArrayInitElem" {
  let funcref = RefType::abs(AbsHeapType::func())
  let array_type = SubType::comp_type(
    CompType::array(
      FieldType::new(StorageType::val_type(ValType::ref_type(funcref)), Var),
    ),
  )
  let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
  let env = { ..empty_env(), global_types: [array_type], }.push_elem(elem)
  let array_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0))),
  )
  let state = make_state(env, [
    array_ref,
    ValType::i32(),
    ValType::i32(),
    ValType::i32(),
  ])
  let result = Typecheck::typecheck(
    Instruction::array_init_elem(TypeIdx::new(0), ElemIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

// =============================================================================
// MEMORY INIT/DATA INSTRUCTIONS
// =============================================================================

///|
test "Typecheck MemoryInit" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let data = Data::new(DataMode::passive(), b"")
  let env = empty_env().push_mem(mem_type).push_data(data)
  let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::memory_init(DataIdx::new(0), MemIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck DataDrop" {
  let data = Data::new(DataMode::passive(), b"")
  let env = empty_env().push_data(data)
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::data_drop(DataIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck DataDrop invalid index" {
  let env = empty_env()
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::data_drop(DataIdx::new(0)),
    state,
  )
  assert_err(result)
}

// =============================================================================
// TABLE INIT/ELEM INSTRUCTIONS
// =============================================================================

///|
test "Typecheck TableInit" {
  let table_type = TableType::new(
    RefType::new(true, HeapType::abs(AbsHeapType::func())),
    Limits::i32(1, None),
  )
  let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
  let env = empty_env().push_table(table_type).push_elem(elem)
  let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::table_init(ElemIdx::new(0), TableIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck ElemDrop" {
  let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
  let env = empty_env().push_elem(elem)
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::elem_drop(ElemIdx::new(0)),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck ElemDrop invalid index" {
  let env = empty_env()
  let state = make_state(env, [])
  let result = Typecheck::typecheck(
    Instruction::elem_drop(ElemIdx::new(0)),
    state,
  )
  assert_err(result)
}

// =============================================================================
// ADDITIONAL NUMERIC INSTRUCTIONS (missing from original tests)
// =============================================================================

///|
test "Typecheck I64LtU" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_lt_u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64GtS" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_gt_s(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64LeS" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_le_s(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64LeU" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_le_u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64GeS" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_ge_s(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64GeU" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_ge_u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64Ctz" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_ctz(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Popcnt" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_popcnt(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64DivS" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_div_s(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64DivU" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_div_u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64RemS" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_rem_s(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64RemU" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_rem_u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64And" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_and(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Or" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_or(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Xor" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_xor(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Shl" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_shl(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64ShrS" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_shr_s(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64ShrU" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_shr_u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Rotl" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_rotl(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Rotr" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64(), ValType::i64()])
  let result = Typecheck::typecheck(Instruction::i64_rotr(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck F64Ne" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64(), ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_ne(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck F64Gt" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64(), ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_gt(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck F64Le" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64(), ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_le(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck F64Ge" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64(), ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_ge(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck F64Ceil" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_ceil(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64Floor" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_floor(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64Trunc" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_trunc(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64Nearest" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_nearest(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64Sqrt" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_sqrt(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64Min" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64(), ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_min(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64Max" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64(), ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_max(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64Copysign" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64(), ValType::f64()])
  let result = Typecheck::typecheck(Instruction::f64_copysign(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F32Trunc" {
  let env = empty_env()
  let state = make_state(env, [ValType::f32()])
  let result = Typecheck::typecheck(Instruction::f32_trunc(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f32()])
}

///|
test "Typecheck F32Nearest" {
  let env = empty_env()
  let state = make_state(env, [ValType::f32()])
  let result = Typecheck::typecheck(Instruction::f32_nearest(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f32()])
}

///|
test "Typecheck I64TruncF32U" {
  let env = empty_env()
  let state = make_state(env, [ValType::f32()])
  let result = Typecheck::typecheck(Instruction::i64_trunc_f32u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64TruncF64U" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::i64_trunc_f64u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck F32ConvertI64U" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64()])
  let result = Typecheck::typecheck(Instruction::f32_convert_i64u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f32()])
}

///|
test "Typecheck F64ConvertI32U" {
  let env = empty_env()
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(Instruction::f64_convert_i32u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck F64ConvertI64U" {
  let env = empty_env()
  let state = make_state(env, [ValType::i64()])
  let result = Typecheck::typecheck(Instruction::f64_convert_i64u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::f64()])
}

///|
test "Typecheck I32TruncSatF64U" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::i32_trunc_sat_f64u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64TruncSatF32U" {
  let env = empty_env()
  let state = make_state(env, [ValType::f32()])
  let result = Typecheck::typecheck(Instruction::i64_trunc_sat_f32u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64TruncSatF64U" {
  let env = empty_env()
  let state = make_state(env, [ValType::f64()])
  let result = Typecheck::typecheck(Instruction::i64_trunc_sat_f64u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

// =============================================================================
// ADDITIONAL LOAD/STORE INSTRUCTIONS
// =============================================================================

///|
test "Typecheck I64Load8U" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::i64_load8u(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Load16S" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::i64_load16s(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Load16U" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::i64_load16u(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I64Load32U" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::i64_load32u(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i64()])
}

///|
test "Typecheck I32Store16" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32(), ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::i32_store16(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck I64Store8" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32(), ValType::i64()])
  let result = Typecheck::typecheck(
    Instruction::i64_store8(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

///|
test "Typecheck I64Store16" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32(), ValType::i64()])
  let result = Typecheck::typecheck(
    Instruction::i64_store16(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [])
}

// =============================================================================
// ADDITIONAL SIMD INSTRUCTIONS
// =============================================================================

///|
test "Typecheck I8x16Shuffle" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let t = Instruction::i8x16_shuffle(
    LaneIdx::new(0),
    LaneIdx::new(1),
    LaneIdx::new(2),
    LaneIdx::new(3),
    LaneIdx::new(4),
    LaneIdx::new(5),
    LaneIdx::new(6),
    LaneIdx::new(7),
    LaneIdx::new(8),
    LaneIdx::new(9),
    LaneIdx::new(10),
    LaneIdx::new(11),
    LaneIdx::new(12),
    LaneIdx::new(13),
    LaneIdx::new(14),
    LaneIdx::new(15),
  )
  let result = Typecheck::typecheck(t, state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I8x16Swizzle" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_swizzle(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128AnyTrue" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::v128_any_true(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I8x16AllTrue" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_all_true(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I8x16Bitmask" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_bitmask(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I16x8Bitmask" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_bitmask(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I32x4Bitmask" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_bitmask(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck I64x2Bitmask" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_bitmask(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::i32()])
}

///|
test "Typecheck V128Or" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::v128_or(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Xor" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::v128_xor(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Andnot" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::v128_andnot(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I8x16Neg" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_neg(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I8x16Popcnt" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_popcnt(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I8x16Sub" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_sub(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I8x16AddSatS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_add_sat_s(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I8x16AddSatU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_add_sat_u(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I64x2Add" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_add(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I64x2Sub" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_sub(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck I64x2Mul" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_mul(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Add" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_add(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Sub" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_sub(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Mul" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_mul(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Add" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_add(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Sub" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_sub(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Mul" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_mul(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Div" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_div(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Abs" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_abs(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Neg" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_neg(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F32x4Sqrt" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_sqrt(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Abs" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_abs(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Neg" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_neg(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck F64x2Sqrt" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_sqrt(), state)
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load32Zero" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load32_zero(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load64Zero" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load64_zero(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load8x8S" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load8x8s(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load8x8U" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load8x8u(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load16x4S" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load16x4s(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load16x4U" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load16x4u(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load32x2S" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load32x2s(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load32x2U" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load32x2u(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load8Splat" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load8_splat(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

///|
test "Typecheck V128Load16Splat" {
  let mem_type = MemType::new(Limits::i32(1, None))
  let env = empty_env().push_mem(mem_type)
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load16_splat(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let final_state = assert_ok(result)
  assert_stack(final_state, [ValType::v128()])
}

// =============================================================================
// SIMD LOAD TESTS (Missing)
// =============================================================================

///|
test "Typecheck V128Load32Splat" {
  let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load32_splat(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck V128Load64Splat" {
  let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
  let state = make_state(env, [ValType::i32()])
  let result = Typecheck::typecheck(
    Instruction::v128_load64_splat(MemArg::new(U32(0), None, U64(0))),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck V128Load32Lane" {
  let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::v128_load32_lane(
      MemArg::new(U32(0), None, U64(0)),
      LaneIdx::new(0),
    ),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck V128Load64Lane" {
  let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::v128_load64_lane(
      MemArg::new(U32(0), None, U64(0)),
      LaneIdx::new(0),
    ),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD STORE TESTS (Missing)
// =============================================================================

///|
test "Typecheck V128Store8Lane" {
  let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::v128_store8_lane(
      MemArg::new(U32(0), None, U64(0)),
      LaneIdx::new(0),
    ),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [])
}

///|
test "Typecheck V128Store32Lane" {
  let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::v128_store32_lane(
      MemArg::new(U32(0), None, U64(0)),
      LaneIdx::new(0),
    ),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [])
}

///|
test "Typecheck V128Store64Lane" {
  let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
  let state = make_state(env, [ValType::i32(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::v128_store64_lane(
      MemArg::new(U32(0), None, U64(0)),
      LaneIdx::new(0),
    ),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [])
}

// =============================================================================
// SIMD CONVERSION TESTS
// =============================================================================

///|
test "Typecheck F32x4DemoteF64x2Zero" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::f32x4_demote_f64x2_zero(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2PromoteLowF32x4" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::f64x2_promote_low_f32x4(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4TruncSatF32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_trunc_sat_f32x4s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4TruncSatF32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_trunc_sat_f32x4u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4ConvertI32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_convert_i32x4s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4ConvertI32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_convert_i32x4u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4TruncSatF64x2SZero" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_trunc_sat_f64x2s_zero(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4TruncSatF64x2UZero" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_trunc_sat_f64x2u_zero(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2ConvertLowI32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::f64x2_convert_low_i32x4s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2ConvertLowI32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::f64x2_convert_low_i32x4u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD i8x16 TESTS (Missing)
// =============================================================================

///|
test "Typecheck I8x16NarrowI16x8S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_narrow_i16x8s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16NarrowI16x8U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_narrow_i16x8u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16Shl" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i8x16_shl(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16ShrS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i8x16_shr_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16ShrU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i8x16_shr_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16SubSatS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_sub_sat_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16SubSatU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_sub_sat_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16MinS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_min_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16MinU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_min_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16MaxS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_max_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16MaxU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_max_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16AvgrU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_avgr_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD i16x8 TESTS (Missing)
// =============================================================================

///|
test "Typecheck I16x8Abs" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_abs(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8AllTrue" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_all_true(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::i32()])
}

///|
test "Typecheck I16x8Q15mulrSatS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8q15mulr_sat_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8NarrowI32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_narrow_i32x4s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8NarrowI32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_narrow_i32x4u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtendLowI8x16S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extend_low_i8x16s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtendHighI8x16S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extend_high_i8x16s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtendLowI8x16U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extend_low_i8x16u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtendHighI8x16U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extend_high_i8x16u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8Shl" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i16x8_shl(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ShrS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i16x8_shr_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ShrU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i16x8_shr_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8Add" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_add(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8AddSatS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_add_sat_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8AddSatU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_add_sat_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8Sub" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_sub(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8SubSatS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_sub_sat_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8SubSatU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_sub_sat_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8MinS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_min_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8MinU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_min_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8MaxS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_max_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8MaxU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_max_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8AvgrU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i16x8_avgr_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtmulLowI8x16S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extmul_low_i8x16s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtmulHighI8x16S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extmul_high_i8x16s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtmulLowI8x16U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extmul_low_i8x16u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtmulHighI8x16U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extmul_high_i8x16u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtaddPairwiseI8x16S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extadd_pairwise_i8x16s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8ExtaddPairwiseI8x16U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_extadd_pairwise_i8x16u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD i32x4 TESTS (Missing)
// =============================================================================

///|
test "Typecheck I32x4Abs" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_abs(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4Neg" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_neg(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtendLowI16x8S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extend_low_i16x8s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtendHighI16x8S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extend_high_i16x8s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtendLowI16x8U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extend_low_i16x8u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtendHighI16x8U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extend_high_i16x8u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4Shl" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i32x4_shl(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ShrS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i32x4_shr_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ShrU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i32x4_shr_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4Add" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_add(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4Mul" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_mul(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4MinS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_min_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4MinU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_min_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4MaxS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_max_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4MaxU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_max_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4DotI16x8S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i32x4_dot_i16x8s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtmulLowI16x8S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extmul_low_i16x8s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtmulHighI16x8S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extmul_high_i16x8s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtmulLowI16x8U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extmul_low_i16x8u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtmulHighI16x8U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extmul_high_i16x8u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtaddPairwiseI16x8S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extadd_pairwise_i16x8s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4ExtaddPairwiseI16x8U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_extadd_pairwise_i16x8u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD i64x2 TESTS (Missing)
// =============================================================================

///|
test "Typecheck I64x2Abs" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_abs(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2Neg" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_neg(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2AllTrue" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_all_true(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::i32()])
}

///|
test "Typecheck I64x2ExtendLowI32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extend_low_i32x4s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ExtendHighI32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extend_high_i32x4s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ExtendLowI32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extend_low_i32x4u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ExtendHighI32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extend_high_i32x4u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2Shl" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i64x2_shl(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ShrS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i64x2_shr_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ShrU" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::i32()])
  let result = Typecheck::typecheck(Instruction::i64x2_shr_u(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2Eq" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_eq(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2Ne" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_ne(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2LtS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_lt_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2GtS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_gt_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2LeS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_le_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2GeS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i64x2_ge_s(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ExtmulLowI32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extmul_low_i32x4s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ExtmulHighI32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extmul_high_i32x4s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ExtmulLowI32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extmul_low_i32x4u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2ExtmulHighI32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i64x2_extmul_high_i32x4u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD f32x4 TESTS (Missing)
// =============================================================================

///|
test "Typecheck F32x4Ceil" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_ceil(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4Floor" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_floor(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4Trunc" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_trunc(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4Nearest" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_nearest(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4Min" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_min(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4Max" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_max(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4Pmin" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_pmin(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4Pmax" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_pmax(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD f64x2 TESTS (Missing)
// =============================================================================

///|
test "Typecheck F64x2Ceil" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_ceil(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2Floor" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_floor(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2Trunc" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_trunc(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2Nearest" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_nearest(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2Min" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_min(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2Max" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_max(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2Pmin" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_pmin(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2Pmax" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_pmax(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

// =============================================================================
// SIMD RELAXED TESTS
// =============================================================================

///|
test "Typecheck I8x16RelaxedSwizzle" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::i8x16_relaxed_swizzle(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4RelaxedTruncF32x4S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_relaxed_trunc_f32x4s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4RelaxedTruncF32x4U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_relaxed_trunc_f32x4u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4RelaxedTruncZeroF64x2S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_relaxed_trunc_zero_f64x2s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4RelaxedTruncZeroF64x2U" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i32x4_relaxed_trunc_zero_f64x2u(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4RelaxedMadd" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(Instruction::f32x4_relaxed_madd(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4RelaxedNmadd" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(Instruction::f32x4_relaxed_nmadd(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2RelaxedMadd" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(Instruction::f64x2_relaxed_madd(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2RelaxedNmadd" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(Instruction::f64x2_relaxed_nmadd(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I8x16RelaxedLaneselect" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(
    Instruction::i8x16_relaxed_laneselect(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8RelaxedLaneselect" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(
    Instruction::i16x8_relaxed_laneselect(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4RelaxedLaneselect" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(
    Instruction::i32x4_relaxed_laneselect(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I64x2RelaxedLaneselect" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(
    Instruction::i64x2_relaxed_laneselect(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4RelaxedMin" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_relaxed_min(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F32x4RelaxedMax" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f32x4_relaxed_max(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2RelaxedMin" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_relaxed_min(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck F64x2RelaxedMax" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(Instruction::f64x2_relaxed_max(), state)
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8RelaxedQ15mulrS" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_relaxed_q15mulr_s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I16x8RelaxedDotI8x16I7x16S" {
  let env = empty_env()
  let state = make_state(env, [ValType::v128(), ValType::v128()])
  let result = Typecheck::typecheck(
    Instruction::i16x8_relaxed_dot_i8x16i7x16s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
test "Typecheck I32x4RelaxedDotI8x16I7x16AddS" {
  let env = empty_env()
  let state = make_state(env, [
    ValType::v128(),
    ValType::v128(),
    ValType::v128(),
  ])
  let result = Typecheck::typecheck(
    Instruction::i32x4_relaxed_dot_i8x16i7x16_add_s(),
    state,
  )
  let state = assert_ok(result)
  assert_stack(state, [ValType::v128()])
}

///|
fn typecheck_array_memory_argument(
  ma : MemArg,
  maximum : UInt32,
) -> Result[Unit, String] {
  let MemArg(U32(align), memory, U64(offset)) = ma
  if memory is Some(_) {
    return Err("array memory argument cannot select a linear memory")
  }
  if offset > 0xffffffffUL {
    return Err("array memory offset exceeds u32")
  }
  if align > maximum {
    return Err("array memory alignment exceeds access width")
  }
  Ok(())
}

///|
fn typecheck_array_load_alignment(op : @lib.LoadOp) -> UInt32 {
  match op {
    @lib.I32Load8SOp | @lib.I32Load8UOp | @lib.I64Load8SOp | @lib.I64Load8UOp =>
      0U
    @lib.I32Load16SOp
    | @lib.I32Load16UOp
    | @lib.I64Load16SOp
    | @lib.I64Load16UOp => 1U
    @lib.I32LoadOp | @lib.F32LoadOp | @lib.I64Load32SOp | @lib.I64Load32UOp =>
      2U
    @lib.I64LoadOp | @lib.F64LoadOp => 3U
    @lib.V128LoadOp => 4U
    _ => 0U
  }
}

///|
fn typecheck_array_store_alignment(op : @lib.StoreOp) -> UInt32 {
  match op {
    @lib.I32Store8Op | @lib.I64Store8Op => 0U
    @lib.I32Store16Op | @lib.I64Store16Op => 1U
    @lib.I32StoreOp | @lib.F32StoreOp | @lib.I64Store32Op => 2U
    @lib.I64StoreOp | @lib.F64StoreOp => 3U
    @lib.V128StoreOp => 4U
    _ => 0U
  }
}

///|
fn typecheck_waitqueue_notify(st : TcState) -> TcResult {
  let queue = ValType::ref_null(HeapType::shared_abs(AbsHeapType::waitqueue()))
  match st.pop_types([queue, ValType::i32()]) {
    Err(err) => Err(err)
    Ok(next) => Ok(next.push1(ValType::i32()))
  }
}

///|
fn typecheck_struct_wait(
  st : TcState,
  type_idx : TypeIdx,
  field : U32,
) -> TcResult {
  let fields = match st.env.resolve_struct_fields(type_idx) {
    Err(err) => return Err(err)
    Ok(fields) => fields
  }
  match get_struct_field(fields, field) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let receiver = ValType::ref_null(HeapType::new(type_idx))
  let queue = ValType::ref_null(HeapType::shared_abs(AbsHeapType::waitqueue()))
  match st.pop_types([receiver, queue, ValType::i32(), ValType::i64()]) {
    Err(err) => Err(err)
    Ok(next) => Ok(next.push1(ValType::i32()))
  }
}

///|
test "expression probe preserves failure without the discarded outer diagnostic" {
  let body = Instruction::block(
    BlockType::void_(),
    Expr::new([Instruction::nop(), Instruction::i32_add()]),
  )
  let direct = match
    Typecheck::typecheck(body, make_state_owned(Env::new(), [])) {
    Err(error) => error
    Ok(_) => abort("intentionally invalid body must fail typechecking")
  }
  let probe = typecheck_expr_for_probe(
    Expr::new([body]),
    make_state_owned(Env::new(), []),
  )
  match probe {
    Err(error) => assert_eq(error, direct)
    Ok(_) => fail("intentionally invalid probe must retain the typing failure")
  }
  // Normal validation retains full context for user-facing diagnostics.
  match
    Typecheck::typecheck(Expr::new([body]), make_state_owned(Env::new(), [])) {
    Err(error) => {
      assert_true(error.contains("instruction[0]"))
      assert_true(error.length() > direct.length())
    }
    Ok(_) => fail("normal validation must also reject the invalid body")
  }
}

///|
test "expression probe retains stack state and first failure" {
  for
    body in [
      [
        Instruction::i32_const(@lib.I32::new(7)),
        Instruction::i32_const(@lib.I32::new(9)),
        Instruction::i32_add(),
      ],
      [
        Instruction::block(
          BlockType::void_(),
          Expr::new([Instruction::br(LabelIdx::new(0U))]),
        ),
        Instruction::i64_const(@lib.I64::new(3L)),
      ],
      [Instruction::unreachable_(), Instruction::i32_add()],
    ] {
    let expected = Typecheck::typecheck(
      Expr::new(body),
      make_state_owned(Env::new(), []),
    ).unwrap()
    let actual = typecheck_expr_for_probe(
      Expr::new(body),
      make_state_owned(Env::new(), []),
    ).unwrap()
    assert_eq(actual.stack, expected.stack)
    assert_eq(actual.reachable, expected.reachable)
    assert_eq(actual.escape, expected.escape)
    assert_eq(actual.reachable_escape_depths, expected.reachable_escape_depths)
    assert_eq(actual.initialized_locals, expected.initialized_locals)
  }
  let body = Expr::new([
    Instruction::i32_add(),
    Instruction::local_get(LocalIdx::new(99U)),
  ])
  let initial = make_state_owned(Env::new(), [])
  let first = Typecheck::typecheck(Instruction::i32_add(), initial).unwrap_err()
  assert_eq(
    typecheck_expr_for_probe(body, make_state_owned(Env::new(), [])).unwrap_err(),
    first,
  )
}