// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2016 WebAssembly Community Group participants
// Counterpart: src/passes/Precompute.cpp (5d704ad5); origin remains unconfirmed.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.

///|
pub fn precompute_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("precompute", invalidates=[
    @ir.HotAnalysis::cfg(),
    @ir.HotAnalysis::dominance(),
    @ir.HotAnalysis::liveness(),
    @ir.HotAnalysis::use_def(),
    @ir.HotAnalysis::effects(),
    @ir.HotAnalysis::loop_info(),
    @ir.HotAnalysis::ssa(),
  ])
}

///|
pub fn precompute_summary() -> String {
  "Evaluate exact scalar expressions, selected control and GC values, and safe dropped computations."
}

///|
pub fn precompute_propagate_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new(
    "precompute-propagate",
    requires=[@ir.HotAnalysis::ssa()],
    invalidates=[
      @ir.HotAnalysis::cfg(),
      @ir.HotAnalysis::dominance(),
      @ir.HotAnalysis::liveness(),
      @ir.HotAnalysis::use_def(),
      @ir.HotAnalysis::effects(),
      @ir.HotAnalysis::loop_info(),
      @ir.HotAnalysis::ssa(),
    ],
  )
}

///|
pub fn precompute_propagate_summary() -> String {
  "Precompute constants, propagate agreeing reaching local writes, then rerun precompute once."
}

///|
fn precompute_replace_node_rehoming_control_label(
  func : @ir.HotFunc,
  node_id : @ir.NodeId,
  replacement : @ir.HotNode,
) -> Unit {
  @ir.hot_replace_node(func, node_id, replacement)
  let label_id = @ir.hot_control_node_label(func, node_id)
  if label_id >= 0 {
    @ir.hot_label_owner_set(func, label_id, node_id)
  }
}

///|
priv struct PrecomputePropagatePrefixReplacement {
  node_id : Int32
  payload : @ir.HotConstPayload
}

///|
priv struct PrecomputePropagateAllocationFold {
  node_id : Int32
  allocation_id : Int32
}

///|
priv struct PrecomputePropagateIdentityFold {
  node_id : Int32
  value : @lib.I32
}

///|
priv struct PrecomputePartialI32Select {
  condition_id : Int32
  true_value : Int32
  false_value : Int32
}

///|
priv struct PrecomputePartialKnownHeapSelect {
  condition_id : Int32
  true_value : PrecomputeKnownHeapValue
  false_value : PrecomputeKnownHeapValue
}

///|
priv enum PrecomputeHeapIdentity {
  HeapNode(Int32)
  HeapGlobal(@lib.GlobalIdx)
} derive(Eq)

///|
priv enum PrecomputeKnownHeapValue {
  KnownEmit(@lib.Instruction)
  KnownStructFields(@lib.TypeIdx, Array[PrecomputeKnownHeapValue])
  KnownStructDefault(@lib.TypeIdx)
  KnownArrayFixed(@lib.TypeIdx, Array[PrecomputeKnownHeapValue])
  KnownArrayFill(@lib.TypeIdx, Array[PrecomputeKnownHeapValue], UInt32)
  KnownArrayDefault(@lib.TypeIdx, UInt32)
}

///|
fn precompute_global_const(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> @ir.HotConstPayload? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::GlobalGet => ()
    _ => return None
  }
  match ctx.module_ctx {
    Some(module_ctx) =>
      @ir.hot_module_resolve_global_const_init(
        module_ctx,
        @lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
      )
    None => None
  }
}

///|
fn precompute_i32_const(func : @ir.HotFunc, node_id : Int32) -> @lib.I32? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Const =>
      match @ir.hot_const_get(func, node.imm0) {
        @ir.HotConstPayload::I32Const(value) => Some(value)
        _ => None
      }
    @ir.HotOp::GlobalGet => None
    _ => None
  }
}

///|
fn precompute_i32_exact_const(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> @lib.I32? {
  match precompute_i32_const(func, node_id) {
    Some(value) => Some(value)
    None =>
      match precompute_global_const(ctx, func, node_id) {
        Some(@ir.HotConstPayload::I32Const(value)) => Some(value)
        _ => None
      }
  }
}

///|
fn precompute_i64_const(func : @ir.HotFunc, node_id : Int32) -> @lib.I64? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Const =>
      match @ir.hot_const_get(func, node.imm0) {
        @ir.HotConstPayload::I64Const(value) => Some(value)
        _ => None
      }
    @ir.HotOp::GlobalGet => None
    _ => None
  }
}

///|
fn precompute_i64_exact_const(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> @lib.I64? {
  match precompute_i64_const(func, node_id) {
    Some(value) => Some(value)
    None =>
      match precompute_global_const(ctx, func, node_id) {
        Some(@ir.HotConstPayload::I64Const(value)) => Some(value)
        _ => None
      }
  }
}

///|
fn precompute_exact_const_instr(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> @lib.Instruction? {
  if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
    match @ir.hot_node_exact_instr(func, node_id) {
      Some(@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)) as value =>
        return value
      _ => ()
    }
  }
  let payload = if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Const | @ir.HotOp::RefNull =>
        Some(@ir.hot_const_get(func, node.imm0))
      @ir.HotOp::GlobalGet => precompute_global_const(ctx, func, node_id)
      _ => None
    }
  } else {
    None
  }
  match payload {
    Some(@ir.HotConstPayload::I32Const(value)) =>
      Some(@lib.Instruction::i32_const(value))
    Some(@ir.HotConstPayload::I64Const(value)) =>
      Some(@lib.Instruction::i64_const(value))
    Some(@ir.HotConstPayload::F32Const(value)) =>
      Some(@lib.Instruction::f32_const(value))
    Some(@ir.HotConstPayload::F64Const(value)) =>
      Some(@lib.Instruction::f64_const(value))
    Some(@ir.HotConstPayload::RefNullConst(value)) =>
      Some(@lib.Instruction::ref_null_type(value))
    Some(@ir.HotConstPayload::StringConst(value)) =>
      Some(@lib.Instruction::string_const(value))
    None => None
  }
}

///|
fn precompute_build_const_instr(
  func : @ir.HotFunc,
  instr : @lib.Instruction,
) -> Int32? {
  match instr {
    @lib.I32Const(value) => Some(@ir.hot_build_const_i32(func, value))
    @lib.I64Const(value) => Some(@ir.hot_build_const_i64(func, value))
    @lib.F32Const(value) => Some(@ir.hot_build_const_f32(func, value))
    @lib.F64Const(value) => Some(@ir.hot_build_const_f64(func, value))
    @lib.RefNull(ref_type) => Some(@ir.hot_build_ref_null(func, ref_type))
    @lib.StringConst(value) => Some(@ir.hot_build_string_const(func, value))
    @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
      Some(@ir.hot_build_simd(func, [@lib.ValType::v128()], instr, []))
    _ => None
  }
}

///|
fn precompute_replace_with_const_instr(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
) -> Bool {
  let temp = match precompute_build_const_instr(func, instr) {
    Some(node_id) => node_id
    None => return false
  }
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_replace_with_const_instr_preserving_first_local_tee(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
) -> Bool {
  if @ir.hot_child_count(func, node_id) == 0 {
    return precompute_replace_with_const_instr(ctx, func, node_id, instr)
  }
  let reference_id = @ir.hot_child_get(func, node_id, 0)
  if reference_id < 0 || !@ir.hot_node_is_live(func, reference_id) {
    return false
  }
  let reference = @ir.hot_node_get(func, reference_id)
  if !(reference.op is @ir.HotOp::LocalTee) &&
    !precompute_is_discardable_value(func, reference_id) &&
    !precompute_fresh_struct_allocation_is_discardable(func, reference_id) &&
    !precompute_fresh_array_allocation_is_discardable(func, reference_id) {
    // Knowing the final allocation does not make its receiver expression pure.
    // Evaluate each operand in order before replacing the proven heap read.
    let result = match
      @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty) {
      [result] => result
      _ => return false
    }
    let constant = match precompute_build_const_instr(func, instr) {
      Some(constant) => constant
      None => return false
    }
    let body : Array[Int32] = []
    for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
      let operand = @ir.hot_child_get(func, node_id, slot)
      if !precompute_is_discardable_value(func, operand) {
        body.push(@ir.hot_build_drop(func, operand))
      }
    }
    body.push(constant)
    let block = @ir.hot_build_block(
      func,
      @lib.BlockType::val_type(result),
      body,
    )
    let replacement = @ir.hot_node_get(func, block)
    @ir.hot_delete_node(func, block)
    precompute_replace_node_rehoming_control_label(func, node_id, replacement)
    pass_mark_mutated(ctx, func)
    return true
  }
  match reference.op {
    @ir.HotOp::LocalTee => ()
    _ => return precompute_replace_with_const_instr(ctx, func, node_id, instr)
  }
  if @ir.hot_child_count(func, reference_id) != 1 {
    return false
  }
  let result = match
    @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty) {
    [result] => result
    _ => return false
  }
  let const_id = match precompute_build_const_instr(func, instr) {
    Some(const_id) => const_id
    None => return false
  }
  let set_id = @ir.hot_build_local_set(
    func,
    reference.imm0,
    @ir.hot_child_get(func, reference_id, 0),
  )
  let block_id = @ir.hot_build_block(func, @lib.BlockType::val_type(result), [
    set_id, const_id,
  ])
  let replacement = @ir.hot_node_get(func, block_id)
  @ir.hot_delete_node(func, block_id)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_replace_with_const_i32(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  value : @lib.I32,
) -> Unit {
  let temp = @ir.hot_build_const_i32(func, value)
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
}

///|
fn precompute_replace_with_const_i64(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  value : @lib.I64,
) -> Unit {
  let temp = @ir.hot_build_const_i64(func, value)
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
}

///|
fn precompute_try_fold_global_get(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match precompute_global_const(ctx, func, node_id) {
    Some(payload) => {
      let temp = match payload {
        @ir.HotConstPayload::I32Const(value) =>
          @ir.hot_build_const_i32(func, value)
        @ir.HotConstPayload::I64Const(value) =>
          @ir.hot_build_const_i64(func, value)
        @ir.HotConstPayload::F32Const(value) =>
          @ir.hot_build_const_f32(func, value)
        @ir.HotConstPayload::F64Const(value) =>
          @ir.hot_build_const_f64(func, value)
        @ir.HotConstPayload::RefNullConst(value) =>
          @ir.hot_build_ref_null(func, value)
        @ir.HotConstPayload::StringConst(_) => return false
      }
      let replacement = @ir.hot_node_get(func, temp)
      @ir.hot_delete_node(func, temp)
      precompute_replace_node_rehoming_control_label(func, node_id, replacement)
      pass_mark_mutated(ctx, func)
      true
    }
    None => false
  }
}

///|
fn precompute_try_fold_unary(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
  operand_id : Int32,
) -> Bool {
  match precompute_exact_const_instr(ctx, func, operand_id) {
    Some(operand) =>
      match precompute_raw_try_fold_unary(operand, instr) {
        Some(folded) =>
          if precompute_replace_with_const_instr(ctx, func, node_id, folded) {
            return true
          }
        None => ()
      }
    None => ()
  }
  match (instr, precompute_i32_exact_const(ctx, func, operand_id)) {
    (@lib.Instruction::I32Eqz, Some(v)) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if v == @lib.I32(0) {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    _ => ()
  }
  match (instr, precompute_i64_exact_const(ctx, func, operand_id)) {
    (@lib.Instruction::I64Eqz, Some(v)) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if v == @lib.I64(0L) {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    _ => false
  }
}

///|
fn precompute_try_fold_binary(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
  lhs_id : Int32,
  rhs_id : Int32,
) -> Bool {
  match
    (
      precompute_exact_const_instr(ctx, func, lhs_id),
      precompute_exact_const_instr(ctx, func, rhs_id),
    ) {
    (Some(lhs), Some(rhs)) =>
      match precompute_raw_try_fold_binary(lhs, rhs, instr) {
        Some(folded) =>
          if precompute_replace_with_const_instr(ctx, func, node_id, folded) {
            return true
          }
        None => ()
      }
    _ => ()
  }
  match
    (
      instr,
      precompute_i32_exact_const(ctx, func, lhs_id),
      precompute_i32_exact_const(ctx, func, rhs_id),
    ) {
    (@lib.Instruction::I32Add, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        @lib.I32(
          (lhs.reinterpret_as_uint() + rhs.reinterpret_as_uint()).reinterpret_as_int(),
        ),
      )
      return true
    }
    (@lib.Instruction::I32Sub, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        @lib.I32(
          (lhs.reinterpret_as_uint() - rhs.reinterpret_as_uint()).reinterpret_as_int(),
        ),
      )
      return true
    }
    (@lib.Instruction::I32Mul, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        @lib.I32(
          (lhs.reinterpret_as_uint() * rhs.reinterpret_as_uint()).reinterpret_as_int(),
        ),
      )
      return true
    }
    (@lib.Instruction::I32And, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(lhs & rhs))
      return true
    }
    (@lib.Instruction::I32Or, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(lhs | rhs))
      return true
    }
    (@lib.Instruction::I32Xor, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(lhs ^ rhs))
      return true
    }
    (@lib.Instruction::I32Shl, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        @lib.I32(
          (lhs.reinterpret_as_uint() << shift.reinterpret_as_int()).reinterpret_as_int(),
        ),
      )
      return true
    }
    (@lib.Instruction::I32ShrU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        @lib.I32(
          (lhs.reinterpret_as_uint() >> shift.reinterpret_as_int()).reinterpret_as_int(),
        ),
      )
      return true
    }
    (@lib.Instruction::I32ShrS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        @lib.I32(lhs >> shift.reinterpret_as_int()),
      )
      return true
    }
    (@lib.Instruction::I32Eq, Some(lhs), Some(rhs)) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs == rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32Ne, Some(lhs), Some(rhs)) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs != rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32LtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs < rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32LtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint() < rhs.reinterpret_as_uint() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32GtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs > rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32GtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint() > rhs.reinterpret_as_uint() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32LeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs <= rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32LeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint() <= rhs.reinterpret_as_uint() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32GeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs >= rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    (@lib.Instruction::I32GeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint() >= rhs.reinterpret_as_uint() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      return true
    }
    _ => ()
  }
  match
    (
      instr,
      precompute_i64_exact_const(ctx, func, lhs_id),
      precompute_i64_exact_const(ctx, func, rhs_id),
    ) {
    (@lib.Instruction::I64Add, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i64(
        ctx,
        func,
        node_id,
        @lib.I64(
          (lhs.reinterpret_as_uint64() + rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
        ),
      )
      true
    }
    (@lib.Instruction::I64Sub, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i64(
        ctx,
        func,
        node_id,
        @lib.I64(
          (lhs.reinterpret_as_uint64() - rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
        ),
      )
      true
    }
    (@lib.Instruction::I64Mul, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i64(
        ctx,
        func,
        node_id,
        @lib.I64(
          (lhs.reinterpret_as_uint64() * rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
        ),
      )
      true
    }
    (@lib.Instruction::I64And, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i64(ctx, func, node_id, @lib.I64(lhs & rhs))
      true
    }
    (@lib.Instruction::I64Or, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i64(ctx, func, node_id, @lib.I64(lhs | rhs))
      true
    }
    (@lib.Instruction::I64Xor, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i64(ctx, func, node_id, @lib.I64(lhs ^ rhs))
      true
    }
    (@lib.Instruction::I64Shl, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      precompute_replace_with_const_i64(
        ctx,
        func,
        node_id,
        @lib.I64(
          (lhs.reinterpret_as_uint64() << shift.to_int()).reinterpret_as_int64(),
        ),
      )
      true
    }
    (@lib.Instruction::I64ShrU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      precompute_replace_with_const_i64(
        ctx,
        func,
        node_id,
        @lib.I64(
          (lhs.reinterpret_as_uint64() >> shift.to_int()).reinterpret_as_int64(),
        ),
      )
      true
    }
    (@lib.Instruction::I64ShrS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      precompute_replace_with_const_i64(
        ctx,
        func,
        node_id,
        @lib.I64(lhs >> shift.to_int()),
      )
      true
    }
    (@lib.Instruction::I64Eq, Some(lhs), Some(rhs)) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs == rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64Ne, Some(lhs), Some(rhs)) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs != rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64LtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs < rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64LtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint64() < rhs.reinterpret_as_uint64() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64GtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs > rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64GtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint64() > rhs.reinterpret_as_uint64() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64LeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs <= rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64LeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint64() <= rhs.reinterpret_as_uint64() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64GeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs >= rhs {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    (@lib.Instruction::I64GeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      precompute_replace_with_const_i32(
        ctx,
        func,
        node_id,
        if lhs.reinterpret_as_uint64() >= rhs.reinterpret_as_uint64() {
          @lib.I32(1)
        } else {
          @lib.I32(0)
        },
      )
      true
    }
    _ => false
  }
}

///|
fn precompute_try_partial_select_unary_parent(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
) -> Bool {
  if @ir.hot_child_count(func, node_id) != 1 ||
    @ir.hot_node_result_arity(func, node_id) != 1 {
    return false
  }
  let select_id = @ir.hot_child_get(func, node_id, 0)
  if select_id < 0 ||
    !@ir.hot_node_is_live(func, select_id) ||
    @ir.hot_child_count(func, select_id) != 3 {
    return false
  }
  match @ir.hot_node_get(func, select_id).op {
    @ir.HotOp::Select => ()
    _ => return false
  }
  let true_value = match
    precompute_exact_const_instr(
      ctx,
      func,
      @ir.hot_child_get(func, select_id, 0),
    ) {
    Some(value) => value
    None => return false
  }
  let false_value = match
    precompute_exact_const_instr(
      ctx,
      func,
      @ir.hot_child_get(func, select_id, 1),
    ) {
    Some(value) => value
    None => return false
  }
  let folded_true = match precompute_raw_try_fold_unary(true_value, instr) {
    Some(value) => value
    None => return false
  }
  let folded_false = match precompute_raw_try_fold_unary(false_value, instr) {
    Some(value) => value
    None => return false
  }
  let next_true = match precompute_build_const_instr(func, folded_true) {
    Some(value) => value
    None => return false
  }
  let next_false = match precompute_build_const_instr(func, folded_false) {
    Some(value) => value
    None => return false
  }
  let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
  if results.length() != 1 {
    return false
  }
  let temp = @ir.hot_build_select(
    func,
    results[0],
    next_true,
    next_false,
    @ir.hot_child_get(func, select_id, 2),
  )
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_partial_i32_select_for_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  depth : Int32,
) -> PrecomputePartialI32Select? {
  if depth > 32 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  if @ir.hot_child_count(func, node_id) == 3 {
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::Select => {
        let true_value = match
          precompute_i32_exact_const(
            ctx,
            func,
            @ir.hot_child_get(func, node_id, 0),
          ) {
          Some(@lib.I32(value)) => value
          None => return None
        }
        let false_value = match
          precompute_i32_exact_const(
            ctx,
            func,
            @ir.hot_child_get(func, node_id, 1),
          ) {
          Some(@lib.I32(value)) => value
          None => return None
        }
        return Some({
          condition_id: @ir.hot_child_get(func, node_id, 2),
          true_value,
          false_value,
        })
      }
      _ => ()
    }
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return None
  }
  let instr = match @ir.hot_node_exact_instr(func, node_id) {
    Some(instr) => instr
    None => return None
  }
  let inner = match
    precompute_partial_i32_select_for_node(
      ctx,
      func,
      @ir.hot_child_get(func, node_id, 0),
      depth + 1,
    ) {
    Some(value) => value
    None => return None
  }
  let fold = fn(value : Int32) -> Int32? {
    match
      precompute_raw_try_fold_unary(
        @lib.Instruction::i32_const(@lib.I32(value)),
        instr,
      ) {
      Some(@lib.I32Const(@lib.I32(value))) => Some(value)
      _ => None
    }
  }
  let true_value = match fold(inner.true_value) {
    Some(value) => value
    None => return None
  }
  let false_value = match fold(inner.false_value) {
    Some(value) => value
    None => return None
  }
  Some({ condition_id: inner.condition_id, true_value, false_value, })
}

///|
fn precompute_try_partial_select_condition(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_child_count(func, node_id) != 3 ||
    @ir.hot_node_result_arity(func, node_id) != 1 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Select => ()
    _ => return false
  }
  let condition = match
    precompute_partial_i32_select_for_node(
      ctx,
      func,
      @ir.hot_child_get(func, node_id, 2),
      0,
    ) {
    Some(value) => value
    None => return false
  }
  let outer_true = match
    precompute_exact_const_instr(ctx, func, @ir.hot_child_get(func, node_id, 0)) {
    Some(value) => value
    None => return false
  }
  let outer_false = match
    precompute_exact_const_instr(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
    Some(value) => value
    None => return false
  }
  let next_true = match
    precompute_build_const_instr(
      func,
      if condition.true_value != 0 {
        outer_true
      } else {
        outer_false
      },
    ) {
    Some(value) => value
    None => return false
  }
  let next_false = match
    precompute_build_const_instr(
      func,
      if condition.false_value != 0 {
        outer_true
      } else {
        outer_false
      },
    ) {
    Some(value) => value
    None => return false
  }
  let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
  if results.length() != 1 {
    return false
  }
  let temp = @ir.hot_build_select(
    func,
    results[0],
    next_true,
    next_false,
    condition.condition_id,
  )
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_fold_constant_select(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Select => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 3 ||
    @ir.hot_node_result_arity(func, node_id) != 1 {
    return false
  }
  let condition_id = @ir.hot_child_get(func, node_id, 2)
  if !precompute_is_discardable_value(func, condition_id) {
    return false
  }
  let select_true = match precompute_i32_exact_const(ctx, func, condition_id) {
    Some(@lib.I32(0)) => false
    Some(@lib.I32(_)) => true
    None => return false
  }
  let selected_slot = if select_true { 0 } else { 1 }
  let unselected_slot = if select_true { 1 } else { 0 }
  let selected_id = @ir.hot_child_get(func, node_id, selected_slot)
  let unselected_id = @ir.hot_child_get(func, node_id, unselected_slot)
  if selected_id < 0 ||
    !@ir.hot_node_is_live(func, selected_id) ||
    !precompute_is_discardable_value(func, unselected_id) {
    return false
  }
  let selected_is_control = @ir.hot_is_control_node(func, selected_id)
  precompute_replace_node_rehoming_control_label(
    func,
    node_id,
    @ir.hot_node_get(func, selected_id),
  )
  if selected_is_control {
    @ir.hot_delete_node(func, selected_id)
  }
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_partial_select_parent(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
) -> Bool {
  if @ir.hot_child_count(func, node_id) == 1 {
    return precompute_try_partial_select_unary_parent(ctx, func, node_id, instr)
  }
  if @ir.hot_child_count(func, node_id) != 2 ||
    @ir.hot_node_result_arity(func, node_id) != 1 {
    return false
  }
  let lhs_id = @ir.hot_child_get(func, node_id, 0)
  let rhs_id = @ir.hot_child_get(func, node_id, 1)
  let lhs_is_select = if lhs_id >= 0 && @ir.hot_node_is_live(func, lhs_id) {
    match @ir.hot_node_get(func, lhs_id).op {
      @ir.HotOp::Select => true
      _ => false
    }
  } else {
    false
  }
  let rhs_is_select = if rhs_id >= 0 && @ir.hot_node_is_live(func, rhs_id) {
    match @ir.hot_node_get(func, rhs_id).op {
      @ir.HotOp::Select => true
      _ => false
    }
  } else {
    false
  }
  let (select_id, other_id, select_is_lhs) = if lhs_is_select {
    (lhs_id, rhs_id, true)
  } else if rhs_is_select {
    (rhs_id, lhs_id, false)
  } else {
    return false
  }
  if @ir.hot_child_count(func, select_id) != 3 {
    return false
  }
  let other = match precompute_exact_const_instr(ctx, func, other_id) {
    Some(value) => value
    None => return false
  }
  let true_id = @ir.hot_child_get(func, select_id, 0)
  let false_id = @ir.hot_child_get(func, select_id, 1)
  let condition_id = @ir.hot_child_get(func, select_id, 2)
  let true_value = match precompute_exact_const_instr(ctx, func, true_id) {
    Some(value) => value
    None => return false
  }
  let false_value = match precompute_exact_const_instr(ctx, func, false_id) {
    Some(value) => value
    None => return false
  }
  let folded_true = match
    (if select_is_lhs {
      precompute_raw_try_fold_binary(true_value, other, instr)
    } else {
      precompute_raw_try_fold_binary(other, true_value, instr)
    }) {
    Some(value) => value
    None => return false
  }
  let folded_false = match
    (if select_is_lhs {
      precompute_raw_try_fold_binary(false_value, other, instr)
    } else {
      precompute_raw_try_fold_binary(other, false_value, instr)
    }) {
    Some(value) => value
    None => return false
  }
  let next_true = match precompute_build_const_instr(func, folded_true) {
    Some(value) => value
    None => return false
  }
  let next_false = match precompute_build_const_instr(func, folded_false) {
    Some(value) => value
    None => return false
  }
  let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
  if results.length() != 1 {
    return false
  }
  let temp = @ir.hot_build_select(
    func,
    results[0],
    next_true,
    next_false,
    condition_id,
  )
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
priv enum PrecomputeEffectfulChildKeep {
  PrecomputeNoEffectKeep
  PrecomputeKeepLocalSet(Int32, Int32)
  PrecomputeKeepDrop(Int32)
}

///|
fn precompute_effectful_child_plan(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  child_id : Int32,
) -> (@lib.Instruction, PrecomputeEffectfulChildKeep)? {
  match precompute_exact_const_instr(ctx, func, child_id) {
    Some(value) => return Some((value, PrecomputeNoEffectKeep))
    None => ()
  }
  if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
    return None
  }
  let child = @ir.hot_node_get(func, child_id)
  match child.op {
    @ir.HotOp::LocalTee => {
      if @ir.hot_child_count(func, child_id) != 1 {
        return None
      }
      let value_id = @ir.hot_child_get(func, child_id, 0)
      match precompute_exact_const_instr(ctx, func, value_id) {
        Some(value) =>
          Some((value, PrecomputeKeepLocalSet(child.imm0, value_id)))
        None => None
      }
    }
    @ir.HotOp::Block => {
      if @ir.hot_node_result_arity(func, child_id) != 1 {
        return None
      }
      let body_ref = @ir.hot_region_body_ref(func, child_id)
      let root_count = @ir.hot_region_root_count(func, body_ref)
      if root_count < 2 {
        return None
      }
      let tail_id = @ir.hot_region_get(func, body_ref, root_count - 1)
      let value = match precompute_exact_const_instr(ctx, func, tail_id) {
        Some(value) => value
        None => return None
      }
      for slot = 0; slot + 1 < root_count; slot = slot + 1 {
        let root_id = @ir.hot_region_get(func, body_ref, slot)
        if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
          return None
        }
        match @ir.hot_node_get(func, root_id).op {
          @ir.HotOp::Nop => ()
          @ir.HotOp::LocalSet | @ir.HotOp::GlobalSet =>
            if @ir.hot_child_count(func, root_id) != 1 ||
              precompute_exact_const_instr(
                ctx,
                func,
                @ir.hot_child_get(func, root_id, 0),
              )
              is None {
              return None
            }
          _ => return None
        }
      }
      Some((value, PrecomputeKeepDrop(child_id)))
    }
    _ => None
  }
}

///|
fn precompute_try_fold_effectful_parent(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
) -> Bool {
  let child_count = @ir.hot_child_count(func, node_id)
  if (child_count != 1 && child_count != 2) ||
    @ir.hot_node_result_arity(func, node_id) != 1 {
    return false
  }
  let plans : Array[(@lib.Instruction, PrecomputeEffectfulChildKeep)] = []
  let mut has_effect = false
  for slot = 0; slot < child_count; slot = slot + 1 {
    let child_id = @ir.hot_child_get(func, node_id, slot)
    let plan = match precompute_effectful_child_plan(ctx, func, child_id) {
      Some(plan) => plan
      None => {
        let child = if child_id >= 0 && @ir.hot_node_is_live(func, child_id) {
          @ir.hot_node_get(func, child_id)
        } else {
          return false
        }
        match child.op {
          @ir.HotOp::LocalGet => {
            let mut prior_value : @lib.Instruction? = None
            for prior_slot = 0; prior_slot < slot; prior_slot = prior_slot + 1 {
              let prior_id = @ir.hot_child_get(func, node_id, prior_slot)
              if prior_id >= 0 && @ir.hot_node_is_live(func, prior_id) {
                let prior = @ir.hot_node_get(func, prior_id)
                match prior.op {
                  @ir.HotOp::LocalTee if prior.imm0 == child.imm0 =>
                    prior_value = Some(plans[prior_slot].0)
                  _ => ()
                }
              }
            }
            match prior_value {
              Some(value) => (value, PrecomputeNoEffectKeep)
              None => return false
            }
          }
          _ => return false
        }
      }
    }
    match plan.1 {
      PrecomputeNoEffectKeep => ()
      _ => has_effect = true
    }
    plans.push(plan)
  }
  if !has_effect {
    return false
  }
  let folded = if child_count == 1 {
    match precompute_raw_try_fold_unary(plans[0].0, instr) {
      Some(value) => value
      None => return false
    }
  } else if child_count == 2 {
    match precompute_raw_try_fold_binary(plans[0].0, plans[1].0, instr) {
      Some(value) => value
      None => return false
    }
  } else if child_count == 3 {
    match
      precompute_raw_try_fold_ternary(plans[0].0, plans[1].0, plans[2].0, instr) {
      Some(value) => value
      None => return false
    }
  } else {
    return false
  }
  let folded_id = match precompute_build_const_instr(func, folded) {
    Some(value) => value
    None => return false
  }
  let kept : Array[Int32] = []
  for plan in plans {
    match plan.1 {
      PrecomputeNoEffectKeep => ()
      PrecomputeKeepLocalSet(local_id, value_id) =>
        kept.push(@ir.hot_build_local_set(func, local_id, value_id))
      PrecomputeKeepDrop(child_id) =>
        kept.push(@ir.hot_build_drop(func, child_id))
    }
  }
  kept.push(folded_id)
  let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
  if results.length() != 1 {
    return false
  }
  let temp = @ir.hot_build_block(
    func,
    @lib.BlockType::val_type(results[0]),
    kept,
  )
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_fresh_struct_allocation_is_discardable(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructNew(_)) | Some(@lib.StructNewDefault(_)) => ()
    Some(@lib.StructNewDesc(_)) | Some(@lib.StructNewDefaultDesc(_)) => {
      let count = @ir.hot_child_count(func, node_id)
      if count == 0 {
        return false
      }
      let descriptor = @ir.hot_child_get(func, node_id, count - 1)
      match @ir.hot_type_results(func, @ir.hot_node_type(func, descriptor)) {
        [@lib.RefTypeValType(ref_type)] if ref_type.is_non_nullable() => ()
        _ => return false
      }
    }
    _ => return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if !precompute_is_discardable_value(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return false
    }
  }
  true
}

///|
fn precompute_global_is_immutable(
  module_ctx : @ir.HotModuleContext,
  global_idx : @lib.GlobalIdx,
) -> Bool {
  match @ir.hot_module_resolve_global_type(module_ctx, global_idx) {
    Some(@lib.GlobalType(_, mutable)) => !mutable
    None => false
  }
}

///|
fn precompute_global_heap_identity(
  module_ctx : @ir.HotModuleContext,
  global_idx : @lib.GlobalIdx,
  depth : Int32,
) -> PrecomputeHeapIdentity? {
  if depth > 32 || !precompute_global_is_immutable(module_ctx, global_idx) {
    return None
  }
  match @ir.hot_module_resolve_global_init(module_ctx, global_idx) {
    Some(@lib.Expr([@lib.GlobalGet(global_alias)])) =>
      precompute_global_heap_identity(module_ctx, global_alias, depth + 1)
    Some(@lib.Expr(instrs)) if instrs.length() > 0 =>
      match instrs[instrs.length() - 1] {
        @lib.StructNew(_)
        | @lib.StructNewDefault(_)
        | @lib.StructNewDesc(_)
        | @lib.StructNewDefaultDesc(_)
        | @lib.ArrayNew(_)
        | @lib.ArrayNewDefault(_)
        | @lib.ArrayNewFixed(_) => Some(HeapGlobal(global_idx))
        _ => None
      }
    _ => None
  }
}

///|
fn precompute_known_heap_pop_values(
  stack : Array[PrecomputeKnownHeapValue],
  count : Int32,
) -> Array[PrecomputeKnownHeapValue]? {
  if count < 0 || stack.length() < count {
    return None
  }
  let start = stack.length() - count
  let values : Array[PrecomputeKnownHeapValue] = []
  for i = 0; i < count; i = i + 1 {
    values.push(stack[start + i])
  }
  while stack.length() > start {
    ignore(stack.pop())
  }
  Some(values)
}

///|
fn precompute_known_heap_value_from_global(
  module_ctx : @ir.HotModuleContext,
  global_idx : @lib.GlobalIdx,
  depth : Int32,
) -> PrecomputeKnownHeapValue? {
  if depth > 32 || !precompute_global_is_immutable(module_ctx, global_idx) {
    return None
  }
  let instrs = match
    @ir.hot_module_resolve_global_init(module_ctx, global_idx) {
    Some(@lib.Expr(instrs)) => instrs
    None => return None
  }
  let stack : Array[PrecomputeKnownHeapValue] = []
  for instr in instrs {
    match instr {
      @lib.I32Const(_)
      | @lib.I64Const(_)
      | @lib.F32Const(_)
      | @lib.F64Const(_)
      | @lib.RefNull(_)
      | @lib.RefFunc(_)
      | @lib.StringConst(_) => stack.push(KnownEmit(instr))
      @lib.GlobalGet(global_alias) =>
        match
          precompute_known_heap_value_from_global(
            module_ctx,
            global_alias,
            depth + 1,
          ) {
          Some(value) => stack.push(value)
          None => return None
        }
      @lib.StructNew(type_idx) => {
        let fields = match
          @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
          Some(fields) => fields
          None => return None
        }
        let values = match
          precompute_known_heap_pop_values(stack, fields.length()) {
          Some(values) => values
          None => return None
        }
        stack.push(KnownStructFields(type_idx, values))
      }
      @lib.StructNewDefault(type_idx) =>
        stack.push(KnownStructDefault(type_idx))
      @lib.ArrayNewFixed(type_idx, @lib.U32(length)) => {
        if !precompute_fresh_array_length_is_interpretable(length) {
          return None
        }
        let values = match
          precompute_known_heap_pop_values(stack, length.reinterpret_as_int()) {
          Some(values) => values
          None => return None
        }
        stack.push(KnownArrayFixed(type_idx, values))
      }
      @lib.ArrayNew(type_idx) => {
        let operands = match precompute_known_heap_pop_values(stack, 2) {
          Some(operands) => operands
          None => return None
        }
        let length = match operands[1] {
          KnownEmit(@lib.I32Const(@lib.I32(length))) if length >= 0 &&
            precompute_fresh_array_length_is_interpretable(
              length.reinterpret_as_uint(),
            ) => length.reinterpret_as_uint()
          _ => return None
        }
        stack.push(KnownArrayFill(type_idx, [operands[0]], length))
      }
      @lib.ArrayNewDefault(type_idx) => {
        let operands = match precompute_known_heap_pop_values(stack, 1) {
          Some(operands) => operands
          None => return None
        }
        let length = match operands[0] {
          KnownEmit(@lib.I32Const(@lib.I32(length))) if length >= 0 &&
            precompute_fresh_array_length_is_interpretable(
              length.reinterpret_as_uint(),
            ) => length.reinterpret_as_uint()
          _ => return None
        }
        stack.push(KnownArrayDefault(type_idx, length))
      }
      _ => return None
    }
  }
  if stack.length() == 1 {
    Some(stack[0])
  } else {
    None
  }
}

///|
fn precompute_global_descriptor_identity(
  module_ctx : @ir.HotModuleContext,
  global_idx : @lib.GlobalIdx,
  depth : Int32,
) -> PrecomputeHeapIdentity? {
  if depth > 32 || !precompute_global_is_immutable(module_ctx, global_idx) {
    return None
  }
  match @ir.hot_module_resolve_global_init(module_ctx, global_idx) {
    Some(@lib.Expr([@lib.GlobalGet(global_alias)])) =>
      precompute_global_descriptor_identity(module_ctx, global_alias, depth + 1)
    Some(@lib.Expr(instrs)) if instrs.length() >= 2 =>
      match instrs[instrs.length() - 1] {
        @lib.StructNewDesc(_) | @lib.StructNewDefaultDesc(_) =>
          match instrs[instrs.length() - 2] {
            @lib.GlobalGet(descriptor) =>
              precompute_global_heap_identity(module_ctx, descriptor, depth + 1)
            _ => None
          }
        _ => None
      }
    _ => None
  }
}

///|
fn precompute_heap_identity_for_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  depth : Int32,
) -> PrecomputeHeapIdentity? {
  if depth > 32 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.RefGetDesc(_)) => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return None
      }
      let source_id = @ir.hot_child_get(func, node_id, 0)
      match @ir.hot_node_exact_instr(func, source_id) {
        Some(@lib.StructNewDesc(_)) | Some(@lib.StructNewDefaultDesc(_)) =>
          if @ir.hot_child_count(func, source_id) > 0 {
            precompute_heap_identity_for_node(
              ctx,
              func,
              @ir.hot_child_get(
                func,
                source_id,
                @ir.hot_child_count(func, source_id) - 1,
              ),
              depth + 1,
            )
          } else {
            None
          }
        _ => {
          let source = @ir.hot_node_get(func, source_id)
          match source.op {
            @ir.HotOp::GlobalGet =>
              match ctx.module_ctx {
                Some(module_ctx) =>
                  precompute_global_descriptor_identity(
                    module_ctx,
                    @lib.GlobalIdx::new(source.imm0.reinterpret_as_uint()),
                    depth + 1,
                  )
                None => None
              }
            _ => None
          }
        }
      }
    }
    _ =>
      match node.op {
        @ir.HotOp::GlobalGet =>
          match ctx.module_ctx {
            Some(module_ctx) =>
              precompute_global_heap_identity(
                module_ctx,
                @lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
                depth + 1,
              )
            None => None
          }
        @ir.HotOp::Block => {
          let label = @ir.hot_control_node_label(func, node_id)
          let body_ref = @ir.hot_region_body_ref(func, node_id)
          for slot = 0
              slot < @ir.hot_region_root_count(func, body_ref)
              slot = slot + 1 {
            let root_id = @ir.hot_region_get(func, body_ref, slot)
            let mut branch_id = root_id
            if root_id >= 0 && @ir.hot_node_is_live(func, root_id) {
              let root = @ir.hot_node_get(func, root_id)
              match root.op {
                @ir.HotOp::Drop if root.child_count == 1 =>
                  branch_id = @ir.hot_child_get(func, root_id, 0)
                _ => ()
              }
            }
            if branch_id >= 0 && @ir.hot_node_is_live(func, branch_id) {
              let branch = @ir.hot_node_get(func, branch_id)
              match branch.op {
                @ir.HotOp::Br if branch.imm0 == label && branch.child_count == 1 =>
                  return precompute_heap_identity_for_node(
                    ctx,
                    func,
                    @ir.hot_child_get(func, branch_id, 0),
                    depth + 1,
                  )
                _ => ()
              }
            }
            if precompute_heap_identity_has_alternative_exit(
                func, root_id, label,
              ) {
              return None
            }
          }
          None
        }
        _ =>
          if precompute_fresh_struct_allocation_is_discardable(func, node_id) ||
            precompute_fresh_array_allocation_is_discardable(func, node_id) {
            Some(HeapNode(node_id))
          } else {
            None
          }
      }
  }
}

///|
fn precompute_descriptor_identity_for_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  depth : Int32,
) -> PrecomputeHeapIdentity? {
  if depth > 32 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructNewDesc(_)) | Some(@lib.StructNewDefaultDesc(_)) =>
      if @ir.hot_child_count(func, node_id) > 0 {
        precompute_heap_identity_for_node(
          ctx,
          func,
          @ir.hot_child_get(
            func,
            node_id,
            @ir.hot_child_count(func, node_id) - 1,
          ),
          depth + 1,
        )
      } else {
        None
      }
    _ => {
      let node = @ir.hot_node_get(func, node_id)
      match node.op {
        @ir.HotOp::GlobalGet =>
          match ctx.module_ctx {
            Some(module_ctx) =>
              precompute_global_descriptor_identity(
                module_ctx,
                @lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
                depth + 1,
              )
            None => None
          }
        _ => None
      }
    }
  }
}

///|
fn precompute_known_heap_value_for_plain_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  depth : Int32,
) -> PrecomputeKnownHeapValue? {
  if depth > 64 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  match precompute_exact_const_instr(ctx, func, node_id) {
    Some(instr) => return Some(KnownEmit(instr))
    None => ()
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::RefFunc =>
      return Some(
        KnownEmit(
          @lib.Instruction::ref_func(
            @lib.FuncIdx::new(node.imm0.reinterpret_as_uint()),
          ),
        ),
      )
    @ir.HotOp::GlobalGet =>
      return match ctx.module_ctx {
        Some(module_ctx) =>
          precompute_known_heap_value_from_global(
            module_ctx,
            @lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
            depth + 1,
          )
        None => None
      }
    _ => ()
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructNew(type_idx)) => {
      let fields = match ctx.module_ctx {
        Some(module_ctx) =>
          match @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
            Some(fields) => fields
            None => return None
          }
        None => return None
      }
      if @ir.hot_child_count(func, node_id) != fields.length() {
        return None
      }
      let values : Array[PrecomputeKnownHeapValue] = []
      for slot = 0; slot < fields.length(); slot = slot + 1 {
        match
          precompute_known_heap_value_for_plain_node(
            ctx,
            func,
            @ir.hot_child_get(func, node_id, slot),
            depth + 1,
          ) {
          Some(value) => values.push(value)
          None => return None
        }
      }
      Some(KnownStructFields(type_idx, values))
    }
    Some(@lib.StructNewDefault(type_idx)) => Some(KnownStructDefault(type_idx))
    Some(@lib.StructGet(type_idx, @lib.U32(field)))
    | Some(@lib.StructGetS(type_idx, @lib.U32(field)))
    | Some(@lib.StructGetU(type_idx, @lib.U32(field))) => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return None
      }
      let source = match
        precompute_known_heap_value_for_plain_node(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 0),
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      let signed = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::struct_get_s(type_idx, @lib.U32(field)))
      let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::struct_get_u(type_idx, @lib.U32(field)))
      precompute_known_heap_struct_get(
        ctx, source, type_idx, field, signed, unsigned,
      )
    }
    Some(@lib.RefCast(nullable, _, target)) => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return None
      }
      let source = match
        precompute_known_heap_value_for_plain_node(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 0),
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      if precompute_known_heap_value_matches_cast(source, nullable, target) {
        Some(source)
      } else {
        None
      }
    }
    _ => None
  }
}

///|
fn precompute_partial_known_heap_select_for_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  depth : Int32,
) -> PrecomputePartialKnownHeapSelect? {
  if depth > 64 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  if @ir.hot_child_count(func, node_id) == 3 {
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::Select => {
        let true_value = match
          precompute_known_heap_value_for_plain_node(
            ctx,
            func,
            @ir.hot_child_get(func, node_id, 0),
            depth + 1,
          ) {
          Some(value) => value
          None => return None
        }
        let false_value = match
          precompute_known_heap_value_for_plain_node(
            ctx,
            func,
            @ir.hot_child_get(func, node_id, 1),
            depth + 1,
          ) {
          Some(value) => value
          None => return None
        }
        return Some({
          condition_id: @ir.hot_child_get(func, node_id, 2),
          true_value,
          false_value,
        })
      }
      _ => ()
    }
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructGet(type_idx, @lib.U32(field)))
    | Some(@lib.StructGetS(type_idx, @lib.U32(field)))
    | Some(@lib.StructGetU(type_idx, @lib.U32(field))) => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return None
      }
      let inner = match
        precompute_partial_known_heap_select_for_node(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 0),
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      let signed = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::struct_get_s(type_idx, @lib.U32(field)))
      let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::struct_get_u(type_idx, @lib.U32(field)))
      let true_value = match
        precompute_known_heap_struct_get(
          ctx,
          inner.true_value,
          type_idx,
          field,
          signed,
          unsigned,
        ) {
        Some(value) => value
        None => return None
      }
      let false_value = match
        precompute_known_heap_struct_get(
          ctx,
          inner.false_value,
          type_idx,
          field,
          signed,
          unsigned,
        ) {
        Some(value) => value
        None => return None
      }
      Some({ condition_id: inner.condition_id, true_value, false_value, })
    }
    _ => None
  }
}

///|
fn precompute_known_heap_ref_is_null(
  value : PrecomputeKnownHeapValue,
) -> Int32? {
  match value {
    KnownEmit(@lib.RefNull(_)) => Some(1)
    KnownEmit(@lib.RefFunc(_))
    | KnownEmit(@lib.StringConst(_))
    | KnownStructFields(_, _)
    | KnownStructDefault(_)
    | KnownArrayFixed(_, _)
    | KnownArrayFill(_, _, _)
    | KnownArrayDefault(_, _) => Some(0)
    _ => None
  }
}

///|
fn precompute_build_known_emit(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  instr : @lib.Instruction,
) -> Int32? {
  match instr {
    @lib.RefFunc(func_idx) => {
      let type_idx = match ctx.module_ctx {
        Some(module_ctx) =>
          match @ir.hot_module_resolve_func_type_idx(module_ctx, func_idx) {
            Some(type_idx) => type_idx
            None => return None
          }
        None => return None
      }
      Some(
        @ir.hot_build_ref_func(
          func,
          func_idx,
          @lib.RefType::new(
            false,
            @lib.HeapType::new(type_idx),
            exact=ctx.module_ctx.unwrap().uses_custom_descriptors,
          ),
        ),
      )
    }
    _ => precompute_build_const_instr(func, instr)
  }
}

///|
fn precompute_try_partial_heap_select_parent(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  let partial : PrecomputePartialKnownHeapSelect = match
    @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::RefIsNull => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return false
      }
      let inner = match
        precompute_partial_known_heap_select_for_node(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 0),
          0,
        ) {
        Some(value) => value
        None => return false
      }
      let true_value = match
        precompute_known_heap_ref_is_null(inner.true_value) {
        Some(value) => KnownEmit(@lib.Instruction::i32_const(@lib.I32(value)))
        None => return false
      }
      let false_value = match
        precompute_known_heap_ref_is_null(inner.false_value) {
        Some(value) => KnownEmit(@lib.Instruction::i32_const(@lib.I32(value)))
        None => return false
      }
      { condition_id: inner.condition_id, true_value, false_value, }
    }
    _ => {
      match @ir.hot_node_exact_instr(func, node_id) {
        Some(@lib.StructGet(_, _))
        | Some(@lib.StructGetS(_, _))
        | Some(@lib.StructGetU(_, _)) => ()
        _ => return false
      }
      match
        precompute_partial_known_heap_select_for_node(ctx, func, node_id, 0) {
        Some(value) => value
        None => return false
      }
    }
  }
  let true_instr = match partial.true_value {
    KnownEmit(instr) => instr
    _ => return false
  }
  let false_instr = match partial.false_value {
    KnownEmit(instr) => instr
    _ => return false
  }
  let true_id = match precompute_build_known_emit(ctx, func, true_instr) {
    Some(value) => value
    None => return false
  }
  let false_id = match precompute_build_known_emit(ctx, func, false_instr) {
    Some(value) => value
    None => return false
  }
  let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
  if results.length() != 1 {
    return false
  }
  let temp = @ir.hot_build_select(
    func,
    results[0],
    true_id,
    false_id,
    partial.condition_id,
  )
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_fold_descriptor_branch(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  let (target, branch_on_equal) = match
    @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.BrOnCastDescEq(_, _, _)) =>
      (@ir.hot_node_get(func, node_id).imm0, true)
    Some(@lib.BrOnCastDescEqFail(_, _, _)) =>
      (@ir.hot_node_get(func, node_id).imm0, false)
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 2 {
    return false
  }
  let source_id = @ir.hot_child_get(func, node_id, 0)
  let descriptor_id = @ir.hot_child_get(func, node_id, 1)
  // Knowing the descriptor's identity does not make its evaluation erasable:
  // a block can perform effects before yielding that same descriptor.
  if !precompute_is_discardable_value(func, descriptor_id) {
    return false
  }
  let source_descriptor = match
    precompute_descriptor_identity_for_node(ctx, func, source_id, 0) {
    Some(identity) => identity
    None => return false
  }
  let supplied_descriptor = match
    precompute_heap_identity_for_node(ctx, func, descriptor_id, 0) {
    Some(identity) => identity
    None => return false
  }
  let taken = (source_descriptor == supplied_descriptor) == branch_on_equal
  if taken {
    let owner = @ir.hot_label_owner(func, target)
    if owner >= 0 &&
      @ir.hot_node_is_live(func, owner) &&
      (match @ir.hot_node_get(func, owner).op {
        @ir.HotOp::Block => true
        _ => false
      }) {
      let body_ref = @ir.hot_region_body_ref(func, owner)
      if @ir.hot_region_root_count(func, body_ref) == 1 &&
        @ir.hot_region_get(func, body_ref, 0) == node_id {
        @ir.hot_region_replace_body(func, body_ref, [source_id])
        @ir.hot_delete_node(func, node_id)
        pass_mark_mutated(ctx, func)
        return true
      }
    }
    let replacement_id = @ir.hot_build_br(func, target, values=[source_id])
    let replacement = @ir.hot_node_get(func, replacement_id)
    @ir.hot_delete_node(func, replacement_id)
    precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  } else {
    let replacement = @ir.hot_node_get(func, source_id)
    precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  }
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_replace_gc_identity_with_const(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  value : @lib.I32,
) -> Bool {
  let kept : Array[Int32] = []
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let child_id = @ir.hot_child_get(func, node_id, slot)
    if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
      return false
    }
    match @ir.hot_node_get(func, child_id).op {
      @ir.HotOp::LocalTee => kept.push(@ir.hot_build_drop(func, child_id))
      _ =>
        if !precompute_is_discardable_value(func, child_id) &&
          !precompute_fresh_struct_allocation_is_discardable(func, child_id) &&
          !precompute_fresh_array_allocation_is_discardable(func, child_id) {
          return false
        }
    }
  }
  if kept.length() == 0 {
    precompute_replace_with_const_i32(ctx, func, node_id, value)
    return true
  }
  kept.push(@ir.hot_build_const_i32(func, value))
  let temp = @ir.hot_build_block(
    func,
    @lib.BlockType::val_type(@lib.ValType::i32()),
    kept,
  )
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_fold_null_ref_cast(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  let (nullable, exact, heap_type) = match
    @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.RefCast(nullable, exact, heap_type)) =>
      (nullable, exact, heap_type)
    _ => return false
  }
  if !nullable || @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let child_id = @ir.hot_child_get(func, node_id, 0)
  if child_id < 0 ||
    !@ir.hot_node_is_live(func, child_id) ||
    (match @ir.hot_node_get(func, child_id).op {
      @ir.HotOp::RefNull => false
      _ => true
    }) {
    return false
  }
  precompute_replace_with_const_instr(
    ctx,
    func,
    node_id,
    @lib.Instruction::ref_null_type(@lib.RefType::new(true, heap_type, exact~)),
  )
}

///|
fn precompute_try_fold_null_aggregate_trap(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructGet(_, _))
    | Some(@lib.StructGetS(_, _))
    | Some(@lib.StructGetU(_, _))
    | Some(@lib.ArrayGet(_))
    | Some(@lib.ArrayGetS(_))
    | Some(@lib.ArrayGetU(_))
    | Some(@lib.ArrayLen) => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) == 0 {
    return false
  }
  let ref_id = @ir.hot_child_get(func, node_id, 0)
  if ref_id < 0 ||
    !@ir.hot_node_is_live(func, ref_id) ||
    (match @ir.hot_node_get(func, ref_id).op {
      @ir.HotOp::RefNull => false
      _ => true
    }) {
    return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if !precompute_is_discardable_value(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return false
    }
  }
  let temp = @ir.hot_build_unreachable(func)
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_fold_ref_is_null(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::RefIsNull => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let ref_id = @ir.hot_child_get(func, node_id, 0)
  if ref_id < 0 || !@ir.hot_node_is_live(func, ref_id) {
    return false
  }
  if @ir.hot_node_get(func, ref_id).op is @ir.HotOp::RefNull {
    return precompute_replace_gc_identity_with_const(
      ctx,
      func,
      node_id,
      @lib.I32(1),
    )
  }
  if precompute_heap_identity_for_node(ctx, func, ref_id, 0) is Some(_) ||
    precompute_fresh_struct_allocation_is_discardable(func, ref_id) ||
    precompute_fresh_array_allocation_is_discardable(func, ref_id) {
    return precompute_replace_gc_identity_with_const(
      ctx,
      func,
      node_id,
      @lib.I32(0),
    )
  }
  false
}

///|
fn precompute_try_partial_ref_eq_select(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.RefEq) => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 2 {
    return false
  }
  let lhs = @ir.hot_child_get(func, node_id, 0)
  let rhs = @ir.hot_child_get(func, node_id, 1)
  let lhs_select = lhs >= 0 &&
    @ir.hot_node_is_live(func, lhs) &&
    (match @ir.hot_node_get(func, lhs).op {
      @ir.HotOp::Select => true
      _ => false
    })
  let rhs_select = rhs >= 0 &&
    @ir.hot_node_is_live(func, rhs) &&
    (match @ir.hot_node_get(func, rhs).op {
      @ir.HotOp::Select => true
      _ => false
    })
  let (select_id, other_id) = if lhs_select {
    (lhs, rhs)
  } else if rhs_select {
    (rhs, lhs)
  } else {
    return false
  }
  if @ir.hot_child_count(func, select_id) != 3 {
    return false
  }
  let other = match precompute_heap_identity_for_node(ctx, func, other_id, 0) {
    Some(value) => value
    None => return false
  }
  let arm_value = fn(slot : Int32) -> Int32? {
    match
      precompute_heap_identity_for_node(
        ctx,
        func,
        @ir.hot_child_get(func, select_id, slot),
        0,
      ) {
      Some(value) => Some(if value == other { 1 } else { 0 })
      None => None
    }
  }
  let true_value = match arm_value(0) {
    Some(value) => value
    None => return false
  }
  let false_value = match arm_value(1) {
    Some(value) => value
    None => return false
  }
  let temp = @ir.hot_build_select(
    func,
    @lib.ValType::i32(),
    @ir.hot_build_const_i32(func, @lib.I32(true_value)),
    @ir.hot_build_const_i32(func, @lib.I32(false_value)),
    @ir.hot_child_get(func, select_id, 2),
  )
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  precompute_replace_node_rehoming_control_label(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_fold_gc_identity(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.RefEq) => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 2 {
    return false
  }
  let lhs = @ir.hot_child_get(func, node_id, 0)
  let rhs = @ir.hot_child_get(func, node_id, 1)
  let lhs_null = lhs >= 0 &&
    @ir.hot_node_is_live(func, lhs) &&
    (match @ir.hot_node_get(func, lhs).op {
      @ir.HotOp::RefNull => true
      _ => false
    })
  let rhs_null = rhs >= 0 &&
    @ir.hot_node_is_live(func, rhs) &&
    (match @ir.hot_node_get(func, rhs).op {
      @ir.HotOp::RefNull => true
      _ => false
    })
  if lhs_null && rhs_null {
    return precompute_replace_gc_identity_with_const(
      ctx,
      func,
      node_id,
      @lib.I32(1),
    )
  }
  let lhs_identity = precompute_heap_identity_for_node(ctx, func, lhs, 0)
  let rhs_identity = precompute_heap_identity_for_node(ctx, func, rhs, 0)
  match (lhs_identity, rhs_identity) {
    (Some(lhs_identity), Some(rhs_identity)) =>
      return precompute_replace_gc_identity_with_const(
        ctx,
        func,
        node_id,
        @lib.I32(if lhs_identity == rhs_identity { 1 } else { 0 }),
      )
    _ => ()
  }
  let lhs_allocation = precompute_fresh_struct_allocation_is_discardable(
      func, lhs,
    ) ||
    precompute_fresh_array_allocation_is_discardable(func, lhs) ||
    lhs_identity is Some(_)
  let rhs_allocation = precompute_fresh_struct_allocation_is_discardable(
      func, rhs,
    ) ||
    precompute_fresh_array_allocation_is_discardable(func, rhs) ||
    rhs_identity is Some(_)
  if (lhs_null && rhs_allocation) || (rhs_null && lhs_allocation) {
    return precompute_replace_gc_identity_with_const(
      ctx,
      func,
      node_id,
      @lib.I32(0),
    )
  }
  if lhs_allocation && rhs_allocation {
    return precompute_replace_gc_identity_with_const(
      ctx,
      func,
      node_id,
      @lib.I32(if lhs == rhs { 1 } else { 0 }),
    )
  }
  false
}

///|
fn precompute_try_fold_fresh_ref_test(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  let target = match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.RefTest(_, _, target)) => target
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let ref_id = @ir.hot_child_get(func, node_id, 0)
  if !precompute_fresh_struct_allocation_is_discardable(func, ref_id) {
    return false
  }
  let type_idx = match @ir.hot_node_exact_instr(func, ref_id) {
    Some(@lib.StructNew(type_idx)) | Some(@lib.StructNewDefault(type_idx)) =>
      type_idx
    _ => return false
  }
  if target != @lib.HeapType::new(type_idx) {
    return false
  }
  precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(1))
  true
}

///|
fn precompute_atomic_aggregate_read_can_fold(
  ctx : HotPassContext,
  order : @lib.AtomicOrder,
  type_idx : @lib.TypeIdx,
) -> Bool {
  match order {
    @lib.SeqCst => false
    @lib.AcqRel | @lib.Relaxed => {
      let module_ctx = match ctx.module_ctx {
        Some(module_ctx) => module_ctx
        None => return false
      }
      match @ir.hot_module_resolve_subtype(module_ctx, type_idx) {
        Some(subtype) => !subtype.metadata().is_shared()
        None => false
      }
    }
  }
}

///|
fn precompute_try_fold_fresh_struct_get_from_allocation(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  allocation_id : Int32,
) -> Bool {
  let (type_idx, raw_field, signed, unsigned, atomic_order) = match
    @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructGet(type_idx, @lib.U32(raw_field))) =>
      (type_idx, raw_field, false, false, None)
    Some(@lib.StructGetS(type_idx, @lib.U32(raw_field))) =>
      (type_idx, raw_field, true, false, None)
    Some(@lib.StructGetU(type_idx, @lib.U32(raw_field))) =>
      (type_idx, raw_field, false, true, None)
    Some(@lib.StructAtomicGet(order, type_idx, @lib.U32(raw_field))) =>
      (type_idx, raw_field, false, false, Some(order))
    Some(@lib.StructAtomicGetS(order, type_idx, @lib.U32(raw_field))) =>
      (type_idx, raw_field, true, false, Some(order))
    Some(@lib.StructAtomicGetU(order, type_idx, @lib.U32(raw_field))) =>
      (type_idx, raw_field, false, true, Some(order))
    _ => return false
  }
  match atomic_order {
    Some(order) =>
      if !precompute_atomic_aggregate_read_can_fold(ctx, order, type_idx) {
        return false
      }
    None => ()
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let ref_id = allocation_id
  let defaulted = match @ir.hot_node_exact_instr(func, ref_id) {
    Some(@lib.StructNew(new_type_idx)) if new_type_idx == type_idx => false
    Some(@lib.StructNewDefault(new_type_idx)) if new_type_idx == type_idx =>
      true
    _ => return false
  }
  let module_ctx = match ctx.module_ctx {
    Some(module_ctx) => module_ctx
    None => return false
  }
  let fields = match
    @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
    Some(fields) => fields
    None => return false
  }
  let field_idx = raw_field.reinterpret_as_int()
  let field = match fields.get(field_idx) {
    Some(field) => field
    None => return false
  }
  if field.is_mutable() {
    return false
  }
  let storage = field.get_storage_type()
  let value = if defaulted {
    if @ir.hot_child_count(func, ref_id) != 0 {
      return false
    }
    match precompute_default_const_for_storage(storage) {
      Some(value) => value
      None => return false
    }
  } else {
    if @ir.hot_child_count(func, ref_id) != fields.length() {
      return false
    }
    for slot = 0; slot < @ir.hot_child_count(func, ref_id); slot = slot + 1 {
      let child_id = @ir.hot_child_get(func, ref_id, slot)
      if slot != field_idx && !precompute_is_discardable_value(func, child_id) {
        return false
      }
    }
    match
      precompute_exact_const_instr(
        ctx,
        func,
        @ir.hot_child_get(func, ref_id, field_idx),
      ) {
      Some(value) => value
      None => return false
    }
  }
  let unpacked = match
    precompute_unpack_array_value(value, storage, signed, unsigned) {
    Some(value) => value
    None => return false
  }
  precompute_replace_with_const_instr_preserving_first_local_tee(
    ctx, func, node_id, unpacked,
  )
}

///|
fn precompute_try_fold_fresh_struct_get(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  precompute_try_fold_fresh_struct_get_from_allocation(
    ctx,
    func,
    node_id,
    @ir.hot_child_get(func, node_id, 0),
  )
}

///|
fn precompute_fresh_array_allocation_is_discardable(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.ArrayNew(_)) | Some(@lib.ArrayNewDefault(_)) => {
      let count = @ir.hot_child_count(func, node_id)
      if count == 0 {
        return false
      }
      let length = @ir.hot_child_get(func, node_id, count - 1)
      match precompute_i32_const(func, length) {
        Some(@lib.I32(value)) if value >= 0 &&
          precompute_fresh_array_length_is_interpretable(
            value.reinterpret_as_uint(),
          ) => ()
        _ => return false
      }
    }
    Some(@lib.ArrayNewFixed(_, _)) => ()
    _ => return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if !precompute_is_discardable_value(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return false
    }
  }
  true
}

///| Binaryen v130's interpreter rejects GC arrays whose literal count reaches

///| its 1 GiB `DataLimit`. The v130 64-bit oracle uses 24-byte `Literal`s, so

///|
/// 44,739,242 is the first rejected length.
fn precompute_fresh_array_length_is_interpretable(length : UInt32) -> Bool {
  length < 44739242U
}

///|
fn precompute_fresh_array_length(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> @lib.I32? {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.ArrayNewFixed(_, @lib.U32(length))) if precompute_fresh_array_length_is_interpretable(
        length,
      ) => Some(@lib.I32(length.reinterpret_as_int()))
    Some(@lib.ArrayNew(_)) if @ir.hot_child_count(func, node_id) == 2 =>
      match
        precompute_i32_exact_const(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 1),
        ) {
        Some(@lib.I32(length)) if length >= 0 &&
          precompute_fresh_array_length_is_interpretable(
            length.reinterpret_as_uint(),
          ) => Some(@lib.I32(length))
        _ => None
      }
    Some(@lib.ArrayNewDefault(_)) if @ir.hot_child_count(func, node_id) == 1 =>
      match
        precompute_i32_exact_const(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 0),
        ) {
        Some(@lib.I32(length)) if length >= 0 &&
          precompute_fresh_array_length_is_interpretable(
            length.reinterpret_as_uint(),
          ) => Some(@lib.I32(length))
        _ => None
      }
    _ => None
  }
}

///|
fn precompute_default_const_for_storage(
  storage : @lib.StorageType,
) -> @lib.Instruction? {
  match storage {
    @lib.PackTypeStorageType(_)
    | @lib.ValTypeStorageType(@lib.NumTypeValType(@lib.I32NumType)) =>
      Some(@lib.Instruction::i32_const(@lib.I32(0)))
    @lib.ValTypeStorageType(@lib.NumTypeValType(@lib.I64NumType)) =>
      Some(@lib.Instruction::i64_const(@lib.I64(0L)))
    @lib.ValTypeStorageType(@lib.NumTypeValType(@lib.F32NumType)) =>
      Some(
        @lib.Instruction::f32_const(@lib.F32(Float::reinterpret_from_uint(0U))),
      )
    @lib.ValTypeStorageType(@lib.NumTypeValType(@lib.F64NumType)) =>
      Some(@lib.Instruction::f64_const(@lib.F64(0.0)))
    @lib.ValTypeStorageType(@lib.RefTypeValType(ref_type)) if ref_type.is_nullable() =>
      Some(@lib.Instruction::ref_null_type(ref_type))
    _ => None
  }
}

///|
fn precompute_fresh_array_get_value(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  allocation_id : Int32,
  type_idx : @lib.TypeIdx,
  index : UInt32,
  storage : @lib.StorageType,
) -> @lib.Instruction? {
  match @ir.hot_node_exact_instr(func, allocation_id) {
    Some(@lib.ArrayNewFixed(new_type_idx, @lib.U32(length))) if new_type_idx ==
      type_idx &&
      precompute_fresh_array_length_is_interpretable(length) &&
      index < length => {
      let slot = index.reinterpret_as_int()
      if @ir.hot_child_count(func, allocation_id) != length.reinterpret_as_int() {
        return None
      }
      precompute_exact_const_instr(
        ctx,
        func,
        @ir.hot_child_get(func, allocation_id, slot),
      )
    }
    Some(@lib.ArrayNew(new_type_idx)) if new_type_idx == type_idx &&
      @ir.hot_child_count(func, allocation_id) == 2 => {
      let length = match
        precompute_i32_exact_const(
          ctx,
          func,
          @ir.hot_child_get(func, allocation_id, 1),
        ) {
        Some(@lib.I32(length)) if length >= 0 &&
          precompute_fresh_array_length_is_interpretable(
            length.reinterpret_as_uint(),
          ) => length.reinterpret_as_uint()
        _ => return None
      }
      if index >= length {
        return None
      }
      precompute_exact_const_instr(
        ctx,
        func,
        @ir.hot_child_get(func, allocation_id, 0),
      )
    }
    Some(@lib.ArrayNewDefault(new_type_idx)) if new_type_idx == type_idx &&
      @ir.hot_child_count(func, allocation_id) == 1 => {
      let length = match
        precompute_i32_exact_const(
          ctx,
          func,
          @ir.hot_child_get(func, allocation_id, 0),
        ) {
        Some(@lib.I32(length)) if length >= 0 &&
          precompute_fresh_array_length_is_interpretable(
            length.reinterpret_as_uint(),
          ) => length.reinterpret_as_uint()
        _ => return None
      }
      if index >= length {
        return None
      }
      precompute_default_const_for_storage(storage)
    }
    _ => None
  }
}

///|
fn precompute_unpack_array_value(
  value : @lib.Instruction,
  storage : @lib.StorageType,
  signed : Bool,
  unsigned : Bool,
) -> @lib.Instruction? {
  match storage {
    @lib.ValTypeStorageType(_) => Some(value)
    @lib.PackTypeStorageType(pack_type) => {
      let raw = match value {
        @lib.I32Const(@lib.I32(raw)) => raw
        _ => return None
      }
      match (pack_type, signed, unsigned) {
        (@lib.I8PackType, true, false) =>
          Some(@lib.Instruction::i32_const(@lib.I32(raw << 24 >> 24)))
        (@lib.I8PackType, false, true) =>
          Some(@lib.Instruction::i32_const(@lib.I32(raw & 0xff)))
        (@lib.I16PackType, true, false) =>
          Some(@lib.Instruction::i32_const(@lib.I32(raw << 16 >> 16)))
        (@lib.I16PackType, false, true) =>
          Some(@lib.Instruction::i32_const(@lib.I32(raw & 0xffff)))
        _ => None
      }
    }
  }
}

///|
fn precompute_try_fold_fresh_array_len_from_allocation(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  allocation_id : Int32,
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.ArrayLen) => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  if !precompute_fresh_array_allocation_is_discardable(func, allocation_id) {
    return false
  }
  let length = match precompute_fresh_array_length(ctx, func, allocation_id) {
    Some(length) => length
    None => return false
  }
  precompute_replace_with_const_instr_preserving_first_local_tee(
    ctx,
    func,
    node_id,
    @lib.Instruction::i32_const(length),
  )
}

///|
fn precompute_try_fold_fresh_array_len(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  precompute_try_fold_fresh_array_len_from_allocation(
    ctx,
    func,
    node_id,
    @ir.hot_child_get(func, node_id, 0),
  )
}

///|
fn precompute_try_fold_fresh_array_get_from_allocation(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  allocation_id : Int32,
) -> Bool {
  let (type_idx, signed, unsigned, atomic_order) = match
    @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.ArrayGet(type_idx)) => (type_idx, false, false, None)
    Some(@lib.ArrayGetS(type_idx)) => (type_idx, true, false, None)
    Some(@lib.ArrayGetU(type_idx)) => (type_idx, false, true, None)
    Some(@lib.ArrayAtomicGet(order, type_idx)) =>
      (type_idx, false, false, Some(order))
    Some(@lib.ArrayAtomicGetS(order, type_idx)) =>
      (type_idx, true, false, Some(order))
    Some(@lib.ArrayAtomicGetU(order, type_idx)) =>
      (type_idx, false, true, Some(order))
    _ => return false
  }
  match atomic_order {
    Some(order) =>
      if !precompute_atomic_aggregate_read_can_fold(ctx, order, type_idx) {
        return false
      }
    None => ()
  }
  if @ir.hot_child_count(func, node_id) != 2 {
    return false
  }
  if !precompute_fresh_array_allocation_is_discardable(func, allocation_id) {
    return false
  }
  let index = match
    precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
    Some(@lib.I32(index)) => index.reinterpret_as_uint()
    None => return false
  }
  let module_ctx = match ctx.module_ctx {
    Some(module_ctx) => module_ctx
    None => return false
  }
  let field = match @ir.hot_module_resolve_array_field(module_ctx, type_idx) {
    Some(field) => field
    None => return false
  }
  if field.is_mutable() {
    return false
  }
  let storage = field.get_storage_type()
  let value = match
    precompute_fresh_array_get_value(
      ctx, func, allocation_id, type_idx, index, storage,
    ) {
    Some(value) => value
    None => return false
  }
  let unpacked = match
    precompute_unpack_array_value(value, storage, signed, unsigned) {
    Some(value) => value
    None => return false
  }
  precompute_replace_with_const_instr_preserving_first_local_tee(
    ctx, func, node_id, unpacked,
  )
}

///|
fn precompute_try_fold_fresh_array_get(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if @ir.hot_child_count(func, node_id) != 2 {
    return false
  }
  precompute_try_fold_fresh_array_get_from_allocation(
    ctx,
    func,
    node_id,
    @ir.hot_child_get(func, node_id, 0),
  )
}

///|
fn precompute_try_fold_string_slice(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StringViewWtf16Slice) => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 3 {
    return false
  }
  let bytes = match
    precompute_exact_const_instr(ctx, func, @ir.hot_child_get(func, node_id, 0)) {
    Some(@lib.StringConst(bytes)) => bytes
    _ => return false
  }
  let start = match
    precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
    Some(@lib.I32(value)) => value
    None => return false
  }
  let end = match
    precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 2)) {
    Some(@lib.I32(value)) => value
    None => return false
  }
  let units = match precompute_string_bytes_to_utf16(bytes) {
    Some(units) => units
    None => return false
  }
  if start < 0 || end < start || end > units.length() {
    return false
  }
  let sliced = match
    precompute_utf16_units_to_valid_utf8(units, start, end - start) {
    Some(bytes) => bytes
    None => return false
  }
  precompute_replace_with_const_instr(
    ctx,
    func,
    node_id,
    @lib.Instruction::string_const(sliced),
  )
}

///|
fn precompute_try_fold_fresh_string_new_wtf16(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StringNewWtf16Array) => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 3 {
    return false
  }
  let allocation = @ir.hot_child_get(func, node_id, 0)
  let count = match @ir.hot_node_exact_instr(func, allocation) {
    Some(@lib.ArrayNewFixed(_, @lib.U32(count))) => count.reinterpret_as_int()
    _ => return false
  }
  if @ir.hot_child_count(func, allocation) != count {
    return false
  }
  let start = match
    precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
    Some(@lib.I32(value)) => value
    None => return false
  }
  let end = match
    precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 2)) {
    Some(@lib.I32(value)) => value
    None => return false
  }
  if start < 0 || end < start || end > count {
    return false
  }
  let units : Array[Int32] = []
  for slot = 0; slot < count; slot = slot + 1 {
    match
      precompute_i32_exact_const(
        ctx,
        func,
        @ir.hot_child_get(func, allocation, slot),
      ) {
      Some(@lib.I32(value)) => units.push(value & 0xffff)
      None => return false
    }
  }
  let bytes = match
    precompute_utf16_units_to_valid_utf8(units, start, end - start) {
    Some(bytes) => bytes
    None => return false
  }
  precompute_replace_with_const_instr(
    ctx,
    func,
    node_id,
    @lib.Instruction::string_const(bytes),
  )
}

///|
fn precompute_constant_if_chosen_region_can_inline(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  let root_count = @ir.hot_region_root_count(func, region_ref)
  if root_count > 1 {
    return false
  }
  if root_count == 1 {
    let root = @ir.hot_region_get(func, region_ref, 0)
    if root >= 0 && @ir.hot_node_is_live(func, root) {
      match @ir.hot_node_get(func, root).op {
        @ir.HotOp::Block | @ir.HotOp::Loop =>
          return @ir.hot_region_root_count(
              func,
              @ir.hot_region_body_ref(func, root),
            ) <=
            1
        _ => ()
      }
    }
  }
  true
}

///|
fn precompute_node_is_integer_zero(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Const =>
      match @ir.hot_const_get(func, @ir.hot_node_get(func, node_id).imm0) {
        @ir.HotConstPayload::I32Const(@lib.I32(0))
        | @ir.HotConstPayload::I64Const(@lib.I64(0L)) => true
        _ => false
      }
    _ => false
  }
}

///|
fn precompute_try_remove_exact_zero_memory_init(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_child_count(func, node_id) != 3 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::MemoryInit => ()
    _ => return false
  }
  for slot = 0; slot < 3; slot = slot + 1 {
    if !precompute_node_is_integer_zero(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return false
    }
  }
  let temp = @ir.hot_build_nop(func)
  let replacement = @ir.hot_node_get(func, temp)
  @ir.hot_delete_node(func, temp)
  @ir.hot_replace_node(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_fold_constant_if(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::If => ()
    _ => return false
  }
  let cond_id = @ir.hot_child_get(func, node_id, 0)
  let take_then = match precompute_i32_exact_const(ctx, func, cond_id) {
    Some(@lib.I32(0)) => false
    Some(@lib.I32(_)) => true
    None => return false
  }
  let chosen_region = if take_then {
    @ir.hot_region_then_ref(func, node_id)
  } else {
    match @ir.hot_region_else_ref(func, node_id) {
      Some(region) => region
      None => {
        @ir.hot_control_demote_if_to_block(func, node_id, [])
        pass_mark_mutated(ctx, func)
        return true
      }
    }
  }
  let root_count = @ir.hot_region_root_count(func, chosen_region)
  if !precompute_constant_if_chosen_region_can_inline(func, chosen_region) {
    return false
  }
  let replacement : Array[Int32] = []
  for i = 0; i < root_count; i = i + 1 {
    replacement.push(@ir.hot_region_get(func, chosen_region, i))
  }
  // The selected arm's roots move to the former if node. Detach them from
  // the old region holder so later subtree deletion sees one live owner.
  @ir.hot_region_replace_body(func, chosen_region, [])
  @ir.hot_control_demote_if_to_block(func, node_id, replacement)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_collapse_first_unreachable_child(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_is_control_node(func, node_id) ||
    @ir.hot_child_count(func, node_id) == 0 {
    return false
  }
  let child_id = @ir.hot_child_get(func, node_id, 0)
  if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
    return false
  }
  if !@ir.hot_is_terminator_node(func, child_id) {
    return false
  }
  precompute_replace_node_rehoming_control_label(
    func,
    node_id,
    @ir.hot_node_get(func, child_id),
  )
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_is_discardable_value(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  let exact_pure_heap = match node.op {
    @ir.HotOp::Heap =>
      match @ir.hot_node_exact_instr(func, node_id) {
        Some(@lib.RefEq)
        | Some(@lib.AnyConvertExtern)
        | Some(@lib.ExternConvertAny)
        | Some(@lib.RefI31) => true
        _ => false
      }
    _ => false
  }
  if (
      !exact_pure_heap &&
      (
        @ir.hot_has_side_effect(func, node_id) ||
        @ir.hot_may_trap(func, node_id)
      )
    ) ||
    @ir.hot_is_control_node(func, node_id) ||
    @ir.hot_is_terminator_node(func, node_id) {
    return false
  }
  if @ir.hot_node_result_arity(func, node_id) == 0 {
    return false
  }
  match node.op {
    @ir.HotOp::Const
    | @ir.HotOp::LocalGet
    | @ir.HotOp::GlobalGet
    | @ir.HotOp::RefNull
    | @ir.HotOp::RefFunc => true
    @ir.HotOp::Unary | @ir.HotOp::Convert => {
      let instr = match @ir.hot_node_exact_instr(func, node_id) {
        Some(instr) => instr
        None => return false
      }
      if !precompute_raw_instr_is_nontrapping_unary_op(instr) {
        return false
      }
      for slot = 0; slot < node.child_count; slot = slot + 1 {
        if !precompute_is_discardable_value(
            func,
            @ir.hot_child_get(func, node_id, slot),
          ) {
          return false
        }
      }
      true
    }
    @ir.HotOp::Binary | @ir.HotOp::Compare => {
      let instr = match @ir.hot_node_exact_instr(func, node_id) {
        Some(instr) => instr
        None => return false
      }
      if !precompute_raw_instr_is_nontrapping_binary_op(instr) {
        return false
      }
      for slot = 0; slot < node.child_count; slot = slot + 1 {
        if !precompute_is_discardable_value(
            func,
            @ir.hot_child_get(func, node_id, slot),
          ) {
          return false
        }
      }
      true
    }
    @ir.HotOp::Heap => {
      let expected_children = match @ir.hot_node_exact_instr(func, node_id) {
        Some(@lib.RefEq) => 2
        Some(@lib.AnyConvertExtern)
        | Some(@lib.ExternConvertAny)
        | Some(@lib.RefI31) => 1
        _ => return false
      }
      if node.child_count != expected_children {
        return false
      }
      for slot = 0; slot < node.child_count; slot = slot + 1 {
        if !precompute_is_discardable_value(
            func,
            @ir.hot_child_get(func, node_id, slot),
          ) {
          return false
        }
      }
      true
    }
    @ir.HotOp::Select => {
      for slot = 0; slot < node.child_count; slot = slot + 1 {
        if !precompute_is_discardable_value(
            func,
            @ir.hot_child_get(func, node_id, slot),
          ) {
          return false
        }
      }
      true
    }
    _ => false
  }
}

///|
fn precompute_try_refinalize_exact_cast_block(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  let current_results = @ir.hot_type_results(
    func,
    @ir.hot_node_get(func, node_id).ty,
  )
  let current_ref = match current_results {
    [@lib.RefTypeValType(result)] if !result.is_exact() => result
    _ => return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  if @ir.hot_region_root_count(func, body_ref) != 2 {
    return false
  }
  let drop_id = @ir.hot_region_get(func, body_ref, 0)
  let tail_id = @ir.hot_region_get(func, body_ref, 1)
  match @ir.hot_node_get(func, tail_id).op {
    @ir.HotOp::Unreachable => ()
    _ => return false
  }
  match @ir.hot_node_get(func, drop_id).op {
    @ir.HotOp::Drop => ()
    _ => return false
  }
  if @ir.hot_child_count(func, drop_id) != 1 {
    return false
  }
  let branch_id = @ir.hot_child_get(func, drop_id, 0)
  if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
    return false
  }
  let (target_nullable, target_heap) = match
    @ir.hot_node_exact_instr(func, branch_id) {
    Some(
      @lib.BrOnCast(_, @lib.CastOp(_, target_nullable, _, true), _, target_heap)
    ) => (target_nullable, target_heap)
    _ => return false
  }
  if current_ref.is_nullable() != target_nullable ||
    current_ref.get_heap_type() != target_heap {
    return false
  }
  let label_id = @ir.hot_control_node_label(func, node_id)
  if label_id < 0 || @ir.hot_branch_target(func, branch_id) != Some(label_id) {
    return false
  }
  if pass_label_is_used(
      label_id,
      pass_compute_label_used_except(func, branch_id),
    ) {
    return false
  }
  let exact_result = @lib.ValType::ref_type(
    @lib.RefType::new(target_nullable, target_heap, exact=true),
  )
  @ir.hot_control_result_type_set(
    func,
    node_id,
    @ir.hot_type_intern_block_result(
      func,
      @lib.BlockType::val_type(exact_result),
    ),
  )
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_simplify_self_branch_value_block(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  let root_count = @ir.hot_region_root_count(func, body_ref)
  if root_count == 0 {
    return false
  }
  let mut first_value = 0
  while first_value < root_count {
    let root_id = @ir.hot_region_get(func, body_ref, first_value)
    if root_id >= 0 &&
      @ir.hot_node_is_live(func, root_id) &&
      (match @ir.hot_node_get(func, root_id).op {
        @ir.HotOp::Nop => true
        _ => false
      }) {
      first_value += 1
    } else {
      break
    }
  }
  if first_value >= root_count {
    return false
  }
  let branch_id = @ir.hot_region_get(func, body_ref, first_value)
  if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
    return false
  }
  let branch = @ir.hot_node_get(func, branch_id)
  match branch.op {
    @ir.HotOp::Br => ()
    _ => {
      if first_value + 1 == root_count && first_value > 0 {
        @ir.hot_region_replace_body(func, body_ref, [branch_id])
        pass_mark_mutated(ctx, func)
        return true
      }
      return false
    }
  }
  if branch.imm0 != @ir.hot_control_node_label(func, node_id) ||
    branch.child_count != 1 {
    return false
  }
  let value_id = @ir.hot_child_get(func, branch_id, 0)
  @ir.hot_region_replace_body(func, body_ref, [value_id])
  @ir.hot_delete_node(func, branch_id)
  pass_mark_mutated(ctx, func)
  true
}

///|
priv struct PrecomputeTransparentBlockCleanup {
  holders : Array[Int32]
  copied_values : Array[Int32]
}

///|
fn PrecomputeTransparentBlockCleanup::new() -> PrecomputeTransparentBlockCleanup {
  { holders: [], copied_values: [], }
}

///|
fn precompute_finish_transparent_block_cleanup(
  func : @ir.HotFunc,
  cleanup : PrecomputeTransparentBlockCleanup,
) -> Unit {
  // Remove copied operands before checking the detached control forest. A
  // copied value may retain edges to controls still owned by its replacement.
  if !cleanup.copied_values.is_empty() {
    @ir.hot_delete_detached_nodes(func, cleanup.copied_values)
  }
  if !cleanup.holders.is_empty() &&
    !@ir.hot_delete_detached_control_forest(func, cleanup.holders) {
    abort("precompute failed to delete detached transparent value blocks")
  }
}

///|
fn precompute_try_inline_transparent_value_block_child(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  label_used : @bitset.BitSet,
  use_counts : @ir.HotNodeUseCounts,
  cleanup~ : PrecomputeTransparentBlockCleanup? = None,
) -> Bool {
  if @ir.hot_is_control_node(func, node_id) {
    return false
  }
  let child_count = @ir.hot_child_count(func, node_id)
  let mut changed = false
  for slot = 0; slot < child_count; slot = slot + 1 {
    let child_id = @ir.hot_child_get(func, node_id, slot)
    if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
      continue
    }
    match @ir.hot_node_get(func, child_id).op {
      @ir.HotOp::Block => ()
      _ => continue
    }
    if @ir.hot_node_result_arity(func, child_id) != 1 {
      continue
    }
    let label_id = @ir.hot_control_node_label(func, child_id)
    if pass_label_is_used(label_id, label_used) {
      continue
    }
    if @ir.hot_node_use_count(use_counts, child_id) != 1 {
      continue
    }
    let body_ref = @ir.hot_region_body_ref(func, child_id)
    if @ir.hot_region_root_count(func, body_ref) != 1 {
      continue
    }
    let value_id = @ir.hot_region_get(func, body_ref, 0)
    if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
      continue
    }
    let prefix_count = @ir.hot_node_get(func, child_id).imm1
    if prefix_count > 0 &&
      !(prefix_count == 1 &&
      @ir.hot_child_count(func, child_id) == 2 &&
      @ir.hot_child_get(func, child_id, 0) == value_id) {
      // Only a scalar identity proves that removing its entry prefix cannot
      // lose input effects. Other typed holders keep their represented entry.
      continue
    }
    if slot == 0 && @ir.hot_is_terminator_node(func, value_id) {
      if @ir.hot_node_use_count(use_counts, value_id) != 1 {
        continue
      }
      precompute_replace_node_rehoming_control_label(
        func,
        node_id,
        @ir.hot_node_get(func, value_id),
      )
      @ir.hot_region_replace_body(func, body_ref, [])
      match cleanup {
        Some(cleanup) => {
          cleanup.holders.push(child_id)
          cleanup.copied_values.push(value_id)
          // These nodes are detached. Exclude them from later candidates in
          // this owned phase snapshot while their checked deletion is pending.
          use_counts.counts[child_id] = 0
          use_counts.counts[value_id] = 0
        }
        None => {
          if !@ir.hot_delete_detached_control_forest(func, [child_id]) {
            abort(
              "precompute failed to delete detached terminating value block",
            )
          }
          @ir.hot_delete_node(func, value_id)
        }
      }
      pass_mark_mutated(ctx, func)
      return true
    }
    if @ir.hot_node_result_arity(func, value_id) != 1 {
      continue
    }
    @ir.hot_child_set(func, node_id, slot, value_id)
    if prefix_count == 0 {
      @ir.hot_region_replace_body(func, body_ref, [])
    } else {
      // The prefix and result refer to the same scalar producer. The parent
      // now owns its remaining reference, so retain that producer once.
      ignore(@ir.hot_replace_child_span(func, child_id, []))
    }
    match cleanup {
      Some(cleanup) => {
        cleanup.holders.push(child_id)
        use_counts.counts[child_id] = 0
      }
      None =>
        if !@ir.hot_delete_detached_control_forest(func, [child_id]) {
          abort("precompute failed to delete detached transparent value block")
        }
    }
    pass_mark_mutated(ctx, func)
    changed = true
  }
  changed
}

///|
fn precompute_try_fold_void_br_if(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::BrIf => ()
    _ => return false
  }
  let label_id = node.imm0
  if label_id < 0 ||
    @ir.hot_label_branch_arity(func, label_id) != 0 ||
    @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let condition_id = @ir.hot_child_get(func, node_id, 0)
  let replacement_id = match
    precompute_i32_exact_const(ctx, func, condition_id) {
    Some(@lib.I32(0)) => @ir.hot_build_nop(func)
    Some(@lib.I32(_)) => @ir.hot_build_br(func, label_id)
    None => return false
  }
  precompute_replace_node_rehoming_control_label(
    func,
    node_id,
    @ir.hot_node_get(func, replacement_id),
  )
  @ir.hot_delete_node(func, replacement_id)
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_try_eliminate_dead_drop(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Drop => ()
    _ => return false
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let value_id = @ir.hot_child_get(func, node_id, 0)
  if !precompute_is_discardable_value(func, value_id) {
    return false
  }
  let nop = @ir.hot_build_nop(func)
  precompute_replace_node_rehoming_control_label(
    func,
    node_id,
    @ir.hot_node_get(func, nop),
  )
  pass_mark_mutated(ctx, func)
  true
}

///|
fn precompute_delete_removed_roots(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  removed : Array[Int32],
) -> Unit {
  if removed.is_empty() {
    return
  }
  // A removed region root may still supply a typed control entry or another
  // region. Check physical references once for this batch, including detached
  // live nodes, instead of assuming each removed root is an exclusive owner.
  let pending = @bitset.BitSet::new(@ir.hot_node_count(func))
  for node in removed {
    if @ir.hot_node_is_live(func, node) {
      pending[node] = true
    }
  }
  for index in 0..<@ir.hot_root_count(func) {
    let root = @ir.hot_root_get(func, index)
    if root >= 0 {
      pending[root] = false
    }
  }
  for node in 0..<@ir.hot_node_count(func) {
    if !@ir.hot_node_is_live(func, node) {
      continue
    }
    let data = @ir.hot_node_get(func, node)
    for slot in 0..= 0 {
        pending[child] = false
      }
    }
  }
  let deletable : Array[Int32] = []
  for node in removed {
    if node >= 0 && node < pending.length() && pending[node] {
      pending[node] = false
      deletable.push(node)
    }
  }
  if !deletable.is_empty() {
    @ir.hot_delete_detached_nodes(func, deletable)
    pass_mark_mutated(ctx, func)
  }
}

///|
fn precompute_simplify_region_roots(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  label_used : @bitset.BitSet,
) -> Bool {
  let mut changed = false
  let mut idx = 0
  while idx < @ir.hot_region_root_count(func, region_ref) {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        changed = precompute_simplify_region_roots(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            label_used,
          ) ||
          changed
      @ir.HotOp::If => {
        changed = precompute_simplify_region_roots(
            ctx,
            func,
            @ir.hot_region_then_ref(func, node_id),
            label_used,
          ) ||
          changed
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_region) =>
            changed = precompute_simplify_region_roots(
                ctx, func, else_region, label_used,
              ) ||
              changed
          None => ()
        }
      }
      @ir.HotOp::Try => {
        changed = precompute_simplify_region_roots(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            label_used,
          ) ||
          changed
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_region) =>
            changed = precompute_simplify_region_roots(
                ctx, func, catch_region, label_used,
              ) ||
              changed
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        changed = precompute_simplify_region_roots(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            label_used,
          ) ||
          changed
        changed = precompute_simplify_region_roots(
            ctx,
            func,
            @ir.hot_region_catch_list_ref(func, node_id),
            label_used,
          ) ||
          changed
      }
      _ => ()
    }
    if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
      let terminates_region = match @ir.hot_node_get(func, node_id).op {
        @ir.HotOp::Br | @ir.HotOp::Return | @ir.HotOp::Unreachable => true
        _ => false
      }
      if terminates_region {
        let root_count = @ir.hot_region_root_count(func, region_ref)
        if idx + 1 < root_count {
          let removed = pass_splice_region(
            ctx,
            func,
            region_ref,
            idx + 1,
            root_count - idx - 1,
            [],
          )
          precompute_delete_removed_roots(ctx, func, removed)
          changed = true
        }
        break
      }
      match @ir.hot_node_get(func, node_id).op {
        @ir.HotOp::Block | @ir.HotOp::Loop => {
          if region_ref == @ir.HotRegionRef::root() &&
            precompute_loop_is_terminal_self_branch_with_result_tail(
              func, node_id, idx,
            ) {
            let root_count = @ir.hot_region_root_count(func, region_ref)
            let removed = pass_splice_region(
              ctx,
              func,
              region_ref,
              idx + 1,
              root_count - idx - 1,
              [@ir.hot_build_unreachable(func)],
            )
            precompute_delete_removed_roots(ctx, func, removed)
            changed = true
            continue
          }
          if region_ref == @ir.HotRegionRef::root() &&
            precompute_loop_is_const_true_self_br_if_with_result_tail(
              ctx, func, node_id, idx,
            ) {
            let label = @ir.hot_control_node_label(func, node_id)
            @ir.hot_region_replace_body(
              func,
              @ir.hot_region_body_ref(func, node_id),
              [@ir.hot_build_br(func, label)],
            )
            let root_count = @ir.hot_region_root_count(func, region_ref)
            let removed = pass_splice_region(
              ctx,
              func,
              region_ref,
              idx + 1,
              root_count - idx - 1,
              [@ir.hot_build_unreachable(func)],
            )
            precompute_delete_removed_roots(ctx, func, removed)
            changed = true
            continue
          }
          if region_ref == @ir.HotRegionRef::root() &&
            precompute_control_is_const_self_br_if_noop(ctx, func, node_id) {
            let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
              @ir.hot_build_nop(func),
            ])
            precompute_delete_removed_roots(ctx, func, removed)
            changed = true
            continue
          }
          if region_ref == @ir.HotRegionRef::root() &&
            @ir.hot_node_result_arity(func, node_id) == 0 &&
            precompute_region_is_all_nops(
              func,
              @ir.hot_region_body_ref(func, node_id),
            ) {
            let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
              @ir.hot_build_nop(func),
            ])
            if !@ir.hot_delete_detached_control_forest(func, removed) {
              abort("precompute failed to delete detached empty root control")
            }
            changed = true
            continue
          } else if region_ref == @ir.HotRegionRef::root() {
            match @ir.hot_node_get(func, node_id).op {
              @ir.HotOp::Block =>
                if precompute_block_is_selector_only_self_targeting_br_table(
                    func, node_id,
                  ) {
                  let removed = pass_splice_region(
                    ctx,
                    func,
                    region_ref,
                    idx,
                    1,
                    [@ir.hot_build_nop(func)],
                  )
                  precompute_delete_removed_roots(ctx, func, removed)
                  changed = true
                  continue
                }
              _ => ()
            }
          }
        }
        @ir.HotOp::Drop =>
          if @ir.hot_child_count(func, node_id) == 1 &&
            precompute_is_discardable_value(
              func,
              @ir.hot_child_get(func, node_id, 0),
            ) {
            let replacement = if region_ref == @ir.HotRegionRef::root() ||
              @ir.hot_region_root_count(func, region_ref) == 1 {
              [@ir.hot_build_nop(func)]
            } else {
              []
            }
            let removed = pass_splice_region(
              ctx, func, region_ref, idx, 1, replacement,
            )
            precompute_delete_removed_roots(ctx, func, removed)
            changed = true
            continue
          }
        @ir.HotOp::If =>
          if @ir.hot_node_result_arity(func, node_id) == 0 {
            let cond_id = @ir.hot_child_get(func, node_id, 0)
            let take_then = match
              precompute_i32_exact_const(ctx, func, cond_id) {
              Some(@lib.I32(0)) => Some(false)
              Some(@lib.I32(_)) => Some(true)
              None => None
            }
            match take_then {
              Some(use_then) => {
                let replacement : Array[Int32] = []
                let chosen_region : @ir.HotRegionRef? = if use_then {
                  Some(@ir.hot_region_then_ref(func, node_id))
                } else {
                  @ir.hot_region_else_ref(func, node_id)
                }
                // A chosen arm may branch to this if's own label. Splicing
                // would detach that target before hot lowering resolves it.
                let can_splice = if pass_label_is_used(
                    @ir.hot_control_node_label(func, node_id),
                    label_used,
                  ) {
                  false
                } else {
                  match chosen_region {
                    Some(chosen_region) =>
                      precompute_constant_if_chosen_region_can_inline(
                        func, chosen_region,
                      )
                    None => true
                  }
                }
                if can_splice {
                  match chosen_region {
                    Some(chosen_region) =>
                      for root_idx = 0
                          root_idx <
                         @ir.hot_region_root_count(func, chosen_region)
                          root_idx = root_idx + 1 {
                        replacement.push(
                          @ir.hot_region_get(func, chosen_region, root_idx),
                        )
                      }
                    None => ()
                  }
                  // Preserve the old if label on a detached empty block before
                  // splicing the chosen arm into the parent region. Deleting the
                  // detached owner would leave the label table pointing at a dead
                  // control node.
                  match chosen_region {
                    Some(region) =>
                      @ir.hot_region_replace_body(func, region, [])
                    None => ()
                  }
                  @ir.hot_control_demote_if_to_block(func, node_id, [])
                  ignore(
                    pass_splice_region(
                      ctx, func, region_ref, idx, 1, replacement,
                    ),
                  )
                  changed = true
                  continue
                }
              }
              None => ()
            }
          }
        _ => ()
      }
    }
    idx += 1
  }
  if changed {
    pass_mark_mutated(ctx, func)
  }
  changed
}

///|
fn precompute_control_is_const_self_br_if_noop(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  control_id : Int32,
) -> Bool {
  if @ir.hot_node_result_arity(func, control_id) != 0 {
    return false
  }
  let op = @ir.hot_node_get(func, control_id).op
  match op {
    @ir.HotOp::Block | @ir.HotOp::Loop => ()
    _ => return false
  }
  let label = @ir.hot_control_node_label(func, control_id)
  if label < 0 || @ir.hot_label_branch_arity(func, label) != 0 {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, control_id)
  if @ir.hot_region_root_count(func, body_ref) != 1 {
    return false
  }
  let branch_id = @ir.hot_region_get(func, body_ref, 0)
  let branch_node = @ir.hot_node_get(func, branch_id)
  match branch_node.op {
    @ir.HotOp::BrIf => ()
    _ => return false
  }
  if branch_node.child_count != 1 || branch_node.imm0 != label {
    return false
  }
  let cond_id = @ir.hot_child_get(func, branch_id, 0)
  match precompute_i32_exact_const(ctx, func, cond_id) {
    Some(@lib.I32(0)) => true
    Some(@lib.I32(_)) =>
      match op {
        @ir.HotOp::Block => true
        _ => false
      }
    None => false
  }
}

///|
fn precompute_root_suffix_has_result(
  func : @ir.HotFunc,
  start_idx : Int32,
) -> Bool {
  let region_ref = @ir.HotRegionRef::root()
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let mut idx = start_idx
  while idx < root_count {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    if @ir.hot_node_is_live(func, root_id) &&
      @ir.hot_node_result_arity(func, root_id) > 0 {
      return true
    }
    idx = idx + 1
  }
  false
}

///|
fn precompute_loop_is_terminal_self_branch_with_result_tail(
  func : @ir.HotFunc,
  loop_id : Int32,
  root_idx : Int32,
) -> Bool {
  match @ir.hot_node_get(func, loop_id).op {
    @ir.HotOp::Loop => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, loop_id) != 0 ||
    !precompute_root_suffix_has_result(func, root_idx + 1) {
    return false
  }
  let label = @ir.hot_control_node_label(func, loop_id)
  if label < 0 || @ir.hot_label_branch_arity(func, label) != 0 {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, loop_id)
  let root_count = @ir.hot_region_root_count(func, body_ref)
  if root_count == 0 {
    return false
  }
  let branch_id = @ir.hot_region_get(func, body_ref, root_count - 1)
  let branch = @ir.hot_node_get(func, branch_id)
  match branch.op {
    @ir.HotOp::Br => branch.child_count == 0 && branch.imm0 == label
    _ => false
  }
}

///|
fn precompute_loop_is_const_true_self_br_if_with_result_tail(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  loop_id : Int32,
  root_idx : Int32,
) -> Bool {
  match @ir.hot_node_get(func, loop_id).op {
    @ir.HotOp::Loop => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, loop_id) != 0 {
    return false
  }
  if !precompute_root_suffix_has_result(func, root_idx + 1) {
    return false
  }
  let label = @ir.hot_control_node_label(func, loop_id)
  if label < 0 || @ir.hot_label_branch_arity(func, label) != 0 {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, loop_id)
  if @ir.hot_region_root_count(func, body_ref) != 1 {
    return false
  }
  let branch_id = @ir.hot_region_get(func, body_ref, 0)
  let branch_node = @ir.hot_node_get(func, branch_id)
  match branch_node.op {
    @ir.HotOp::BrIf => ()
    _ => return false
  }
  if branch_node.child_count != 1 || branch_node.imm0 != label {
    return false
  }
  let cond_id = @ir.hot_child_get(func, branch_id, 0)
  match precompute_i32_exact_const(ctx, func, cond_id) {
    Some(@lib.I32(0)) | None => false
    Some(@lib.I32(_)) => true
  }
}

///|
fn precompute_block_is_selector_only_self_targeting_br_table(
  func : @ir.HotFunc,
  block_id : Int32,
) -> Bool {
  if @ir.hot_node_result_arity(func, block_id) != 0 {
    return false
  }
  let block_label = @ir.hot_control_node_label(func, block_id)
  if block_label < 0 || @ir.hot_label_branch_arity(func, block_label) != 0 {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, block_id)
  if @ir.hot_region_root_count(func, body_ref) != 1 {
    return false
  }
  let br_table_id = @ir.hot_region_get(func, body_ref, 0)
  match @ir.hot_node_get(func, br_table_id).op {
    @ir.HotOp::BrTable => ()
    _ => return false
  }
  if @ir.hot_child_count(func, br_table_id) != 1 ||
    !precompute_is_discardable_value(
      func,
      @ir.hot_child_get(func, br_table_id, 0),
    ) {
    return false
  }
  if @ir.hot_branch_target(func, br_table_id) != Some(block_label) {
    return false
  }
  for target in @ir.hot_branch_table_targets_for_node(func, br_table_id) {
    if target != block_label {
      return false
    }
  }
  true
}

///|
fn precompute_region_is_all_nops(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::Nop => ()
      _ => return false
    }
  }
  true
}

///|
fn precompute_trim_region_nops(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  let mut changed = false
  let mut idx = 0
  while idx < @ir.hot_region_root_count(func, region_ref) {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        changed = precompute_trim_region_nops(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
          ) ||
          changed
      @ir.HotOp::If => {
        changed = precompute_trim_region_nops(
            ctx,
            func,
            @ir.hot_region_then_ref(func, node_id),
          ) ||
          changed
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_region) =>
            changed = precompute_trim_region_nops(ctx, func, else_region) ||
              changed
          None => ()
        }
      }
      @ir.HotOp::Try => {
        changed = precompute_trim_region_nops(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
          ) ||
          changed
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_region) =>
            changed = precompute_trim_region_nops(ctx, func, catch_region) ||
              changed
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        changed = precompute_trim_region_nops(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
          ) ||
          changed
        changed = precompute_trim_region_nops(
            ctx,
            func,
            @ir.hot_region_catch_list_ref(func, node_id),
          ) ||
          changed
      }
      _ => ()
    }
    if idx >= @ir.hot_region_root_count(func, region_ref) {
      break
    }
    if region_ref == @ir.HotRegionRef::root() {
      idx += 1
      continue
    }
    let current_id = @ir.hot_region_get(func, region_ref, idx)
    match @ir.hot_node_get(func, current_id).op {
      @ir.HotOp::Nop => {
        let mut run_end = idx + 1
        let root_count = @ir.hot_region_root_count(func, region_ref)
        while run_end < root_count {
          let run_id = @ir.hot_region_get(func, region_ref, run_end)
          match @ir.hot_node_get(func, run_id).op {
            @ir.HotOp::Nop => run_end += 1
            _ => break
          }
        }
        ignore(
          pass_splice_region(ctx, func, region_ref, idx, run_end - idx, []),
        )
        changed = true
        continue
      }
      _ => ()
    }
    idx += 1
  }
  changed
}

///|
fn precompute_node_is_nop_tree(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Nop => true
    @ir.HotOp::Block | @ir.HotOp::Loop => {
      let body_ref = @ir.hot_region_body_ref(func, node_id)
      // A control node's entry operands precede its body roots in the child
      // span. They may trap or have effects even when the body is all nops.
      @ir.hot_child_count(func, node_id) ==
      @ir.hot_region_root_count(func, body_ref) &&
      precompute_region_is_all_nops(func, body_ref)
    }
    _ => false
  }
}

///|
fn precompute_coalesce_all_root_nops(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> Bool {
  let region_ref = @ir.HotRegionRef::root()
  let root_count = @ir.hot_region_root_count(func, region_ref)
  if root_count <= 1 {
    return false
  }
  for idx = 0; idx < root_count; idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if !precompute_node_is_nop_tree(func, node_id) {
      return false
    }
  }
  ignore(pass_splice_region(ctx, func, region_ref, 1, root_count - 1, []))
  true
}

///|
fn precompute_trim_root_nops_before_trailing_const(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> Bool {
  let region_ref = @ir.HotRegionRef::root()
  let root_count = @ir.hot_region_root_count(func, region_ref)
  if root_count <= 1 {
    return false
  }
  let tail_id = @ir.hot_region_get(func, region_ref, root_count - 1)
  if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
    return false
  }
  match @ir.hot_node_get(func, tail_id).op {
    @ir.HotOp::Const => ()
    _ => return false
  }
  for idx = 0; idx + 1 < root_count; idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::Nop => ()
      _ => return false
    }
  }
  ignore(pass_splice_region(ctx, func, region_ref, 0, root_count - 1, []))
  true
}

///|
fn precompute_raw_i32_const(instr : @lib.Instruction) -> @lib.I32? {
  match instr {
    @lib.I32Const(value) => Some(value)
    _ => None
  }
}

///|
fn precompute_raw_i64_const(instr : @lib.Instruction) -> @lib.I64? {
  match instr {
    @lib.I64Const(value) => Some(value)
    _ => None
  }
}

///|
fn precompute_raw_f32_const(instr : @lib.Instruction) -> @lib.F32? {
  match instr {
    @lib.F32Const(value) => Some(value)
    _ => None
  }
}

///|
fn precompute_raw_f64_const(instr : @lib.Instruction) -> @lib.F64? {
  match instr {
    @lib.F64Const(value) => Some(value)
    _ => None
  }
}

///|
fn precompute_raw_global_const_instr(
  module_ctx : @ir.HotModuleContext?,
  global_idx : @lib.GlobalIdx,
) -> @lib.Instruction? {
  let payload = match module_ctx {
    Some(ctx) => @ir.hot_module_resolve_global_const_init(ctx, global_idx)
    None => None
  }
  match payload {
    Some(@ir.HotConstPayload::I32Const(value)) =>
      Some(@lib.Instruction::i32_const(value))
    Some(@ir.HotConstPayload::I64Const(value)) =>
      Some(@lib.Instruction::i64_const(value))
    Some(@ir.HotConstPayload::F32Const(value)) =>
      Some(@lib.Instruction::f32_const(value))
    Some(@ir.HotConstPayload::F64Const(value)) =>
      Some(@lib.Instruction::f64_const(value))
    Some(@ir.HotConstPayload::RefNullConst(value)) =>
      Some(@lib.Instruction::ref_null_type(value))
    Some(@ir.HotConstPayload::StringConst(_)) | None => None
  }
}

///|
fn precompute_raw_bool(value : Bool) -> @lib.Instruction {
  @lib.Instruction::i32_const(if value { @lib.I32(1) } else { @lib.I32(0) })
}

///|
fn precompute_raw_f32_nearest(value : Float) -> Float {
  if value.is_nan() ||
    value >= Float::from_double(8388608.0) ||
    value <= Float::from_double(-8388608.0) {
    return value
  }
  let lower = value.floor()
  let fraction = value - lower
  let rounded = if fraction < Float::from_double(0.5) {
    lower
  } else if fraction > Float::from_double(0.5) {
    lower + Float::from_double(1.0)
  } else if (lower.to_int() & 1) == 0 {
    lower
  } else {
    lower + Float::from_double(1.0)
  }
  if rounded == Float::from_double(0.0) &&
    (value.reinterpret_as_uint() & 0x80000000U) != 0U {
    Float::reinterpret_from_uint(0x80000000U)
  } else {
    rounded
  }
}

///|
fn precompute_raw_f64_nearest(value : Double) -> Double {
  if value.is_nan() ||
    value >= 4503599627370496.0 ||
    value <= -4503599627370496.0 {
    return value
  }
  let lower = value.floor()
  let fraction = value - lower
  let rounded = if fraction < 0.5 {
    lower
  } else if fraction > 0.5 {
    lower + 1.0
  } else if (lower.to_int64() & 1L) == 0L {
    lower
  } else {
    lower + 1.0
  }
  if rounded == 0.0 &&
    (value.reinterpret_as_uint64() & 0x8000000000000000UL) != 0UL {
    0x8000000000000000UL.reinterpret_as_double()
  } else {
    rounded
  }
}

///|
fn precompute_raw_try_fold_i32_trunc(
  value : Double,
  signed : Bool,
  saturating : Bool,
) -> @lib.Instruction? {
  if signed {
    if saturating {
      let result = if value.is_nan() {
        0
      } else if value <= -2147483648.0 {
        -2147483647 - 1
      } else if value >= 2147483648.0 {
        2147483647
      } else {
        value.to_int()
      }
      return Some(@lib.Instruction::i32_const(@lib.I32(result)))
    }
    if value.is_nan() || value <= -2147483649.0 || value >= 2147483648.0 {
      return None
    }
    // Truncation removes the fractional part before the range check.
    let result = if value < -2147483648.0 {
      -2147483647 - 1
    } else {
      value.to_int()
    }
    return Some(@lib.Instruction::i32_const(@lib.I32(result)))
  }
  if saturating {
    let result = if value.is_nan() || value <= 0.0 {
      0U
    } else if value >= 4294967296.0 {
      0xffffffffU
    } else {
      value.to_uint()
    }
    return Some(
      @lib.Instruction::i32_const(@lib.I32(result.reinterpret_as_int())),
    )
  }
  if value.is_nan() || value <= -1.0 || value >= 4294967296.0 {
    return None
  }
  let result = if value < 0.0 { 0U } else { value.to_uint() }
  Some(@lib.Instruction::i32_const(@lib.I32(result.reinterpret_as_int())))
}

///|
fn precompute_trunc_double_to_uint64(value : Double) -> UInt64 {
  if value < 9223372036854775808.0 {
    value.to_int64().reinterpret_as_uint64()
  } else {
    (value - 9223372036854775808.0).to_int64().reinterpret_as_uint64() |
    0x8000000000000000UL
  }
}

///|
fn precompute_raw_try_fold_i64_trunc(
  value : Double,
  signed : Bool,
  saturating : Bool,
) -> @lib.Instruction? {
  if signed {
    if saturating {
      let result = if value.is_nan() {
        0L
      } else if value <= -9223372036854775808.0 {
        -9223372036854775807L - 1L
      } else if value >= 9223372036854775808.0 {
        9223372036854775807L
      } else {
        value.to_int64()
      }
      return Some(@lib.Instruction::i64_const(@lib.I64(result)))
    }
    if value.is_nan() ||
      value < -9223372036854775808.0 ||
      value >= 9223372036854775808.0 {
      return None
    }
    return Some(@lib.Instruction::i64_const(@lib.I64(value.to_int64())))
  }
  if saturating {
    let result = if value.is_nan() || value <= 0.0 {
      0UL
    } else if value >= 18446744073709551616.0 {
      0xffffffffffffffffUL
    } else {
      precompute_trunc_double_to_uint64(value)
    }
    return Some(
      @lib.Instruction::i64_const(@lib.I64(result.reinterpret_as_int64())),
    )
  }
  if value.is_nan() || value <= -1.0 || value >= 18446744073709551616.0 {
    return None
  }
  let result = if value < 0.0 {
    0UL
  } else {
    precompute_trunc_double_to_uint64(value)
  }
  Some(@lib.Instruction::i64_const(@lib.I64(result.reinterpret_as_int64())))
}

///|
fn precompute_v128_const_bytes(instr : @lib.Instruction) -> Array[Byte]? {
  match instr {
    @lib.V128Const(
      b0,
      b1,
      b2,
      b3,
      b4,
      b5,
      b6,
      b7,
      b8,
      b9,
      b10,
      b11,
      b12,
      b13,
      b14,
      b15
    ) =>
      Some([
        b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15,
      ])
    _ => None
  }
}

///|
fn precompute_v128_read_u32(bytes : Array[Byte], offset : Int32) -> UInt32 {
  bytes[offset].to_uint() |
  (bytes[offset + 1].to_uint() << 8) |
  (bytes[offset + 2].to_uint() << 16) |
  (bytes[offset + 3].to_uint() << 24)
}

///|
fn precompute_v128_read_u64(bytes : Array[Byte], offset : Int32) -> UInt64 {
  precompute_v128_read_u32(bytes, offset).to_uint64() |
  (precompute_v128_read_u32(bytes, offset + 4).to_uint64() << 32)
}

///|
fn precompute_v128_write_u32(
  bytes : Array[Byte],
  offset : Int32,
  value : UInt32,
) -> Unit {
  bytes[offset] = value.to_byte()
  bytes[offset + 1] = (value >> 8).to_byte()
  bytes[offset + 2] = (value >> 16).to_byte()
  bytes[offset + 3] = (value >> 24).to_byte()
}

///|
fn precompute_raw_try_fold_v128_lane_extract(
  operand : @lib.Instruction,
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  match instr {
    @lib.I8x16ExtractLaneS(lane) => {
      let lane = lane.0.to_int()
      if lane >= 16 {
        return None
      }
      let value = (bytes[lane].to_uint() << 24).reinterpret_as_int() >> 24
      Some(@lib.Instruction::i32_const(@lib.I32(value)))
    }
    @lib.I8x16ExtractLaneU(lane) => {
      let lane = lane.0.to_int()
      if lane >= 16 {
        return None
      }
      Some(
        @lib.Instruction::i32_const(
          @lib.I32(bytes[lane].to_uint().reinterpret_as_int()),
        ),
      )
    }
    @lib.I16x8ExtractLaneS(lane) => {
      let lane = lane.0.to_int()
      if lane >= 8 {
        return None
      }
      let offset = lane * 2
      let raw = bytes[offset].to_uint() | (bytes[offset + 1].to_uint() << 8)
      Some(
        @lib.Instruction::i32_const(
          @lib.I32((raw << 16).reinterpret_as_int() >> 16),
        ),
      )
    }
    @lib.I16x8ExtractLaneU(lane) => {
      let lane = lane.0.to_int()
      if lane >= 8 {
        return None
      }
      let offset = lane * 2
      let raw = bytes[offset].to_uint() | (bytes[offset + 1].to_uint() << 8)
      Some(@lib.Instruction::i32_const(@lib.I32(raw.reinterpret_as_int())))
    }
    @lib.I32x4ExtractLane(lane) => {
      let lane = lane.0.to_int()
      if lane >= 4 {
        return None
      }
      Some(
        @lib.Instruction::i32_const(
          @lib.I32(
            precompute_v128_read_u32(bytes, lane * 4).reinterpret_as_int(),
          ),
        ),
      )
    }
    @lib.I64x2ExtractLane(lane) => {
      let lane = lane.0.to_int()
      if lane >= 2 {
        return None
      }
      Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            precompute_v128_read_u64(bytes, lane * 8).reinterpret_as_int64(),
          ),
        ),
      )
    }
    @lib.F32x4ExtractLane(lane) => {
      let lane = lane.0.to_int()
      if lane >= 4 {
        return None
      }
      Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            Float::reinterpret_from_uint(
              precompute_v128_read_u32(bytes, lane * 4),
            ),
          ),
        ),
      )
    }
    @lib.F64x2ExtractLane(lane) => {
      let lane = lane.0.to_int()
      if lane >= 2 {
        return None
      }
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            precompute_v128_read_u64(bytes, lane * 8).reinterpret_as_double(),
          ),
        ),
      )
    }
    _ => None
  }
}

///|
fn precompute_raw_try_fold_v128_reduction(
  operand : @lib.Instruction,
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let result = match instr {
    @lib.V128AnyTrue => {
      let mut any = false
      for byte in bytes {
        if byte != b'\x00' {
          any = true
        }
      }
      if any {
        1
      } else {
        0
      }
    }
    @lib.I8x16AllTrue => {
      let mut all = true
      for byte in bytes {
        if byte == b'\x00' {
          all = false
        }
      }
      if all {
        1
      } else {
        0
      }
    }
    @lib.I8x16Bitmask => {
      let mut mask = 0
      for lane = 0; lane < 16; lane = lane + 1 {
        if (bytes[lane].to_uint() & 0x80U) != 0U {
          mask = mask | (1 << lane)
        }
      }
      mask
    }
    @lib.I16x8AllTrue => {
      let mut all = true
      for lane = 0; lane < 8; lane = lane + 1 {
        let offset = lane * 2
        if bytes[offset] == b'\x00' && bytes[offset + 1] == b'\x00' {
          all = false
        }
      }
      if all {
        1
      } else {
        0
      }
    }
    @lib.I16x8Bitmask => {
      let mut mask = 0
      for lane = 0; lane < 8; lane = lane + 1 {
        if (bytes[lane * 2 + 1].to_uint() & 0x80U) != 0U {
          mask = mask | (1 << lane)
        }
      }
      mask
    }
    @lib.I32x4AllTrue => {
      let mut all = true
      for lane = 0; lane < 4; lane = lane + 1 {
        if precompute_v128_read_u32(bytes, lane * 4) == 0U {
          all = false
        }
      }
      if all {
        1
      } else {
        0
      }
    }
    @lib.I32x4Bitmask => {
      let mut mask = 0
      for lane = 0; lane < 4; lane = lane + 1 {
        if (precompute_v128_read_u32(bytes, lane * 4) & 0x80000000U) != 0U {
          mask = mask | (1 << lane)
        }
      }
      mask
    }
    @lib.I64x2AllTrue => {
      let mut all = true
      for lane = 0; lane < 2; lane = lane + 1 {
        let offset = lane * 8
        if precompute_v128_read_u32(bytes, offset) == 0U &&
          precompute_v128_read_u32(bytes, offset + 4) == 0U {
          all = false
        }
      }
      if all {
        1
      } else {
        0
      }
    }
    @lib.I64x2Bitmask => {
      let mut mask = 0
      for lane = 0; lane < 2; lane = lane + 1 {
        if (precompute_v128_read_u32(bytes, lane * 8 + 4) & 0x80000000U) != 0U {
          mask = mask | (1 << lane)
        }
      }
      mask
    }
    _ => return None
  }
  Some(@lib.Instruction::i32_const(@lib.I32(result)))
}

///|
fn precompute_v128_const_from_bytes(bytes : Array[Byte]) -> @lib.Instruction? {
  if bytes.length() != 16 {
    return None
  }
  Some(
    @lib.Instruction::v128_const(
      bytes[0],
      bytes[1],
      bytes[2],
      bytes[3],
      bytes[4],
      bytes[5],
      bytes[6],
      bytes[7],
      bytes[8],
      bytes[9],
      bytes[10],
      bytes[11],
      bytes[12],
      bytes[13],
      bytes[14],
      bytes[15],
    ),
  )
}

///|
fn precompute_v128_write_u64(
  bytes : Array[Byte],
  offset : Int32,
  value : UInt64,
) -> Unit {
  for byte_idx = 0; byte_idx < 8; byte_idx = byte_idx + 1 {
    bytes[offset + byte_idx] = (value >> (byte_idx * 8)).to_byte()
  }
}

///|
fn precompute_raw_try_fold_f64x2_unary(
  operand : @lib.Instruction,
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  let scalar_instr = match instr {
    @lib.F64x2Floor => @lib.Instruction::f64_floor()
    @lib.F64x2Nearest => @lib.Instruction::f64_nearest()
    _ => return None
  }
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let offset = lane * 8
    let value = precompute_v128_read_u64(bytes, offset).reinterpret_as_double()
    let folded = match
      precompute_raw_try_fold_unary(
        @lib.Instruction::f64_const(@lib.F64(value)),
        scalar_instr,
      ) {
      Some(@lib.F64Const(@lib.F64(result))) => result.reinterpret_as_uint64()
      _ => return None
    }
    precompute_v128_write_u64(out, offset, folded)
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f32x4_convert_i32x4_s(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 4; lane = lane + 1 {
    let offset = lane * 4
    let value = precompute_v128_read_u32(bytes, offset).reinterpret_as_int()
    precompute_v128_write_u32(
      out,
      offset,
      Float::from_int(value).reinterpret_as_uint(),
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f32x4_convert_i32x4_u(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 4; lane = lane + 1 {
    let offset = lane * 4
    let value = precompute_v128_read_u32(bytes, offset)
    precompute_v128_write_u32(
      out,
      offset,
      Float::from_uint(value).reinterpret_as_uint(),
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f64x2_eq(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let offset = lane * 8
    let lhs_value = precompute_v128_read_u64(lhs, offset).reinterpret_as_double()
    let rhs_value = precompute_v128_read_u64(rhs, offset).reinterpret_as_double()
    precompute_v128_write_u64(
      out,
      offset,
      if lhs_value == rhs_value {
        0xffffffffffffffffUL
      } else {
        0UL
      },
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i8x16_shl(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let shift = match rhs_instr {
    @lib.I32Const(@lib.I32(value)) => value & 7
    _ => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 16; lane = lane + 1 {
    out[lane] = (lhs[lane].to_uint() << shift).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i8x16_shr_s(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let shift = match rhs_instr {
    @lib.I32Const(@lib.I32(value)) => value & 7
    _ => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 16; lane = lane + 1 {
    let signed = (lhs[lane].to_uint() << 24).reinterpret_as_int() >> 24
    out[lane] = (signed >> shift).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_v128_xor(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for index = 0; index < 16; index = index + 1 {
    out[index] = (lhs[index].to_uint() ^ rhs[index].to_uint()).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_v128_bitselect(
  first_instr : @lib.Instruction,
  second_instr : @lib.Instruction,
  mask_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let first = match precompute_v128_const_bytes(first_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let second = match precompute_v128_const_bytes(second_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let mask = match precompute_v128_const_bytes(mask_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for byte_idx = 0; byte_idx < 16; byte_idx = byte_idx + 1 {
    out[byte_idx] = ((first[byte_idx].to_uint() & mask[byte_idx].to_uint()) |
    (second[byte_idx].to_uint() & (mask[byte_idx].to_uint() ^ 0xffU))).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_ternary(
  first_instr : @lib.Instruction,
  second_instr : @lib.Instruction,
  third_instr : @lib.Instruction,
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  match instr {
    @lib.V128Bitselect =>
      precompute_raw_try_fold_v128_bitselect(
        first_instr, second_instr, third_instr,
      )
    _ => None
  }
}

///|
fn precompute_raw_try_fold_f32x4_minmax(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
  scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 4; lane = lane + 1 {
    let offset = lane * 4
    let lhs_value = @lib.Instruction::f32_const(
      @lib.F32(
        Float::reinterpret_from_uint(precompute_v128_read_u32(lhs, offset)),
      ),
    )
    let rhs_value = @lib.Instruction::f32_const(
      @lib.F32(
        Float::reinterpret_from_uint(precompute_v128_read_u32(rhs, offset)),
      ),
    )
    let folded = match
      precompute_raw_try_fold_binary(lhs_value, rhs_value, scalar_op) {
      Some(@lib.F32Const(@lib.F32(value))) => value.reinterpret_as_uint()
      _ => return None
    }
    precompute_v128_write_u32(out, offset, folded)
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_v128_replace_lane(
  vector : @lib.Instruction,
  scalar : @lib.Instruction,
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(vector) {
    Some(bytes) => bytes
    None => return None
  }
  match (instr, scalar) {
    (@lib.I8x16ReplaceLane(lane), @lib.I32Const(@lib.I32(value))) => {
      let lane = lane.0.to_int()
      if lane >= 16 {
        return None
      }
      bytes[lane] = value.to_byte()
    }
    (@lib.I16x8ReplaceLane(lane), @lib.I32Const(@lib.I32(value))) => {
      let lane = lane.0.to_int()
      if lane >= 8 {
        return None
      }
      let offset = lane * 2
      let bits = value.reinterpret_as_uint()
      bytes[offset] = bits.to_byte()
      bytes[offset + 1] = (bits >> 8).to_byte()
    }
    (@lib.I32x4ReplaceLane(lane), @lib.I32Const(@lib.I32(value))) => {
      let lane = lane.0.to_int()
      if lane >= 4 {
        return None
      }
      precompute_v128_write_u32(bytes, lane * 4, value.reinterpret_as_uint())
    }
    (@lib.I64x2ReplaceLane(lane), @lib.I64Const(@lib.I64(value))) => {
      let lane = lane.0.to_int()
      if lane >= 2 {
        return None
      }
      precompute_v128_write_u64(bytes, lane * 8, value.reinterpret_as_uint64())
    }
    (@lib.F32x4ReplaceLane(lane), @lib.F32Const(@lib.F32(value))) => {
      let lane = lane.0.to_int()
      if lane >= 4 {
        return None
      }
      precompute_v128_write_u32(bytes, lane * 4, value.reinterpret_as_uint())
    }
    (@lib.F64x2ReplaceLane(lane), @lib.F64Const(@lib.F64(value))) => {
      let lane = lane.0.to_int()
      if lane >= 2 {
        return None
      }
      precompute_v128_write_u64(bytes, lane * 8, value.reinterpret_as_uint64())
    }
    _ => return None
  }
  precompute_v128_const_from_bytes(bytes)
}

///|
fn precompute_raw_try_fold_f64x2_minmax(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
  scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let offset = lane * 8
    let lhs_value = @lib.Instruction::f64_const(
      @lib.F64(precompute_v128_read_u64(lhs, offset).reinterpret_as_double()),
    )
    let rhs_value = @lib.Instruction::f64_const(
      @lib.F64(precompute_v128_read_u64(rhs, offset).reinterpret_as_double()),
    )
    let folded = match
      precompute_raw_try_fold_binary(lhs_value, rhs_value, scalar_op) {
      Some(@lib.F64Const(@lib.F64(value))) => value.reinterpret_as_uint64()
      _ => return None
    }
    precompute_v128_write_u64(out, offset, folded)
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i64x2_abs(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let offset = lane * 8
    let bits = precompute_v128_read_u64(bytes, offset)
    precompute_v128_write_u64(
      out,
      offset,
      if (bits & 0x8000000000000000UL) != 0UL {
        0UL - bits
      } else {
        bits
      },
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i64x2_neg(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let offset = lane * 8
    precompute_v128_write_u64(
      out,
      offset,
      0UL - precompute_v128_read_u64(bytes, offset),
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f64x2_neg(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = bytes.copy()
  out[7] = (out[7].to_uint() ^ 0x80U).to_byte()
  out[15] = (out[15].to_uint() ^ 0x80U).to_byte()
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i64x2_extend_low_i32x4_s(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let value = precompute_v128_read_u32(bytes, lane * 4)
      .reinterpret_as_int()
      .to_int64()
      .reinterpret_as_uint64()
    precompute_v128_write_u64(out, lane * 8, value)
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f32x4_abs(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = bytes.copy()
  for lane = 0; lane < 4; lane = lane + 1 {
    let sign_byte = lane * 4 + 3
    out[sign_byte] = (out[sign_byte].to_uint() & 0x7fU).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f32x4_unary(
  operand : @lib.Instruction,
  scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 4; lane = lane + 1 {
    let offset = lane * 4
    let scalar = @lib.Instruction::f32_const(
      @lib.F32(
        Float::reinterpret_from_uint(precompute_v128_read_u32(bytes, offset)),
      ),
    )
    let folded = match precompute_raw_try_fold_unary(scalar, scalar_op) {
      Some(@lib.F32Const(@lib.F32(value))) => value.reinterpret_as_uint()
      _ => return None
    }
    precompute_v128_write_u32(out, offset, folded)
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i16x8_narrow_i32x4_u(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 8; lane = lane + 1 {
    let source = if lane < 4 { lhs } else { rhs }
    let source_lane = if lane < 4 { lane } else { lane - 4 }
    let value = precompute_v128_read_u32(source, source_lane * 4).reinterpret_as_int()
    let narrowed : UInt32 = if value < 0 {
      0U
    } else if value > 0xffff {
      0xffffU
    } else {
      value.reinterpret_as_uint()
    }
    out[lane * 2] = narrowed.to_byte()
    out[lane * 2 + 1] = (narrowed >> 8).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_s(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 8; lane = lane + 1 {
    let lhs = (bytes[lane * 2].to_uint() << 24).reinterpret_as_int() >> 24
    let rhs = (bytes[lane * 2 + 1].to_uint() << 24).reinterpret_as_int() >> 24
    let value = lhs + rhs
    out[lane * 2] = value.to_byte()
    out[lane * 2 + 1] = (value >> 8).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_u(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 8; lane = lane + 1 {
    let value = bytes[lane * 2].to_uint() + bytes[lane * 2 + 1].to_uint()
    out[lane * 2] = value.to_byte()
    out[lane * 2 + 1] = (value >> 8).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_v128_not(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for index = 0; index < 16; index = index + 1 {
    out[index] = (bytes[index].to_uint() ^ 0xffU).to_byte()
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f64x2_promote_low_f32x4(
  operand : @lib.Instruction,
) -> @lib.Instruction? {
  let bytes = match precompute_v128_const_bytes(operand) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let scalar = @lib.Instruction::f32_const(
      @lib.F32(
        Float::reinterpret_from_uint(precompute_v128_read_u32(bytes, lane * 4)),
      ),
    )
    let folded = match
      precompute_raw_try_fold_unary(scalar, @lib.Instruction::f64_promote_f32()) {
      Some(@lib.F64Const(@lib.F64(value))) => value.reinterpret_as_uint64()
      _ => return None
    }
    precompute_v128_write_u64(out, lane * 8, folded)
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f32x4_eq(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 4; lane = lane + 1 {
    let offset = lane * 4
    let lhs_value = Float::reinterpret_from_uint(
      precompute_v128_read_u32(lhs, offset),
    )
    let rhs_value = Float::reinterpret_from_uint(
      precompute_v128_read_u32(rhs, offset),
    )
    precompute_v128_write_u32(
      out,
      offset,
      if lhs_value == rhs_value {
        0xffffffffU
      } else {
        0U
      },
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i8x16_ne(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 16; lane = lane + 1 {
    out[lane] = if lhs[lane] != rhs[lane] { b'\xff' } else { b'\x00' }
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_i64x2_ge_s(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let offset = lane * 8
    let folded = precompute_v128_read_u64(lhs, offset).reinterpret_as_int64() >=
      precompute_v128_read_u64(rhs, offset).reinterpret_as_int64()
    precompute_v128_write_u64(
      out,
      offset,
      if folded {
        0xffffffffffffffffUL
      } else {
        0UL
      },
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_f64x2_compare(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
  scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
  let lhs = match precompute_v128_const_bytes(lhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let rhs = match precompute_v128_const_bytes(rhs_instr) {
    Some(bytes) => bytes
    None => return None
  }
  let out = Array::make(16, b'\x00')
  for lane = 0; lane < 2; lane = lane + 1 {
    let offset = lane * 8
    let lhs_value = @lib.Instruction::f64_const(
      @lib.F64(precompute_v128_read_u64(lhs, offset).reinterpret_as_double()),
    )
    let rhs_value = @lib.Instruction::f64_const(
      @lib.F64(precompute_v128_read_u64(rhs, offset).reinterpret_as_double()),
    )
    let folded = match
      precompute_raw_try_fold_binary(lhs_value, rhs_value, scalar_op) {
      Some(@lib.I32Const(@lib.I32(value))) => value != 0
      _ => return None
    }
    precompute_v128_write_u64(
      out,
      offset,
      if folded {
        0xffffffffffffffffUL
      } else {
        0UL
      },
    )
  }
  precompute_v128_const_from_bytes(out)
}

///|
fn precompute_raw_try_fold_unary(
  operand : @lib.Instruction,
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  match instr {
    @lib.I64x2Abs =>
      match precompute_raw_try_fold_i64x2_abs(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.I64x2Neg =>
      match precompute_raw_try_fold_i64x2_neg(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.F64x2Neg =>
      match precompute_raw_try_fold_f64x2_neg(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.I64x2ExtendLowI32x4S =>
      match precompute_raw_try_fold_i64x2_extend_low_i32x4_s(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.F32x4Abs =>
      match precompute_raw_try_fold_f32x4_abs(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.F32x4Floor =>
      match
        precompute_raw_try_fold_f32x4_unary(
          operand,
          @lib.Instruction::f32_floor(),
        ) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.F32x4Ceil =>
      match
        precompute_raw_try_fold_f32x4_unary(
          operand,
          @lib.Instruction::f32_ceil(),
        ) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.F32x4Trunc =>
      match
        precompute_raw_try_fold_f32x4_unary(
          operand,
          @lib.Instruction::f32_trunc(),
        ) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.I16x8ExtaddPairwiseI8x16S =>
      match precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_s(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.I16x8ExtaddPairwiseI8x16U =>
      match precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_u(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.V128Not =>
      match precompute_raw_try_fold_v128_not(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.F64x2PromoteLowF32x4 =>
      match precompute_raw_try_fold_f64x2_promote_low_f32x4(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    _ => ()
  }
  match precompute_raw_try_fold_f64x2_unary(operand, instr) {
    Some(folded) => return Some(folded)
    None => ()
  }
  match instr {
    @lib.F32x4ConvertI32x4S =>
      match precompute_raw_try_fold_f32x4_convert_i32x4_s(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    @lib.F32x4ConvertI32x4U =>
      match precompute_raw_try_fold_f32x4_convert_i32x4_u(operand) {
        Some(folded) => return Some(folded)
        None => ()
      }
    _ => ()
  }
  match precompute_raw_try_fold_v128_lane_extract(operand, instr) {
    Some(folded) => return Some(folded)
    None => ()
  }
  match precompute_raw_try_fold_v128_reduction(operand, instr) {
    Some(folded) => return Some(folded)
    None => ()
  }
  match (instr, operand) {
    (@lib.I8x16Splat, @lib.I32Const(@lib.I32(value))) => {
      let bytes = Array::make(16, value.to_byte())
      return precompute_v128_const_from_bytes(bytes)
    }
    (@lib.I16x8Splat, @lib.I32Const(@lib.I32(value))) => {
      let bits = value.reinterpret_as_uint()
      let bytes = Array::makei(16, fn(index) {
        (bits >> (index % 2 * 8)).to_byte()
      })
      return precompute_v128_const_from_bytes(bytes)
    }
    (@lib.I64x2Splat, @lib.I64Const(@lib.I64(value))) => {
      let bytes = Array::make(16, b'\x00')
      for offset in [0, 8] {
        precompute_v128_write_u64(bytes, offset, value.reinterpret_as_uint64())
      }
      return precompute_v128_const_from_bytes(bytes)
    }
    (@lib.F32x4Splat, @lib.F32Const(@lib.F32(value))) => {
      let bytes = Array::make(16, b'\x00')
      for offset in [0, 4, 8, 12] {
        precompute_v128_write_u32(bytes, offset, value.reinterpret_as_uint())
      }
      return precompute_v128_const_from_bytes(bytes)
    }
    (@lib.F64x2Splat, @lib.F64Const(@lib.F64(value))) => {
      let bytes = Array::make(16, b'\x00')
      for offset in [0, 8] {
        precompute_v128_write_u64(bytes, offset, value.reinterpret_as_uint64())
      }
      return precompute_v128_const_from_bytes(bytes)
    }
    (@lib.I32x4Splat, @lib.I32Const(@lib.I32(value))) => {
      let bytes = Array::make(16, b'\x00')
      let bits = value.reinterpret_as_uint()
      for lane = 0; lane < 4; lane = lane + 1 {
        precompute_v128_write_u32(bytes, lane * 4, bits)
      }
      return precompute_v128_const_from_bytes(bytes)
    }
    (@lib.RefCast(true, exact, heap_type), @lib.RefNull(_)) =>
      return Some(
        @lib.Instruction::ref_null_type(
          @lib.RefType::new(true, heap_type, exact~),
        ),
      )
    (@lib.RefIsNull, @lib.RefNull(_)) =>
      return Some(@lib.Instruction::i32_const(@lib.I32(1)))
    (@lib.StringAsWtf16, @lib.StringConst(_)) => return Some(operand)
    (@lib.StringMeasureWtf16, @lib.StringConst(bytes)) =>
      match precompute_string_bytes_to_utf16(bytes) {
        Some(units) =>
          return Some(@lib.Instruction::i32_const(@lib.I32(units.length())))
        None => return None
      }
    _ => ()
  }
  match (instr, precompute_raw_i32_const(operand)) {
    (@lib.Instruction::I32Eqz, Some(v)) =>
      return Some(precompute_raw_bool(v == @lib.I32(0)))
    (@lib.Instruction::I32Clz, Some(@lib.I32(v))) =>
      return Some(
        @lib.Instruction::i32_const(@lib.I32(v.reinterpret_as_uint().clz())),
      )
    (@lib.Instruction::I32Ctz, Some(@lib.I32(v))) =>
      return Some(
        @lib.Instruction::i32_const(@lib.I32(v.reinterpret_as_uint().ctz())),
      )
    (@lib.Instruction::I32Popcnt, Some(@lib.I32(v))) =>
      return Some(
        @lib.Instruction::i32_const(@lib.I32(v.reinterpret_as_uint().popcnt())),
      )
    (@lib.Instruction::I64ExtendI32S, Some(@lib.I32(v))) =>
      return Some(@lib.Instruction::i64_const(@lib.I64(v.to_int64())))
    (@lib.Instruction::I64ExtendI32U, Some(@lib.I32(v))) =>
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(v.reinterpret_as_uint().to_uint64().reinterpret_as_int64()),
        ),
      )
    (@lib.Instruction::F32ConvertI32S, Some(@lib.I32(v))) =>
      return Some(@lib.Instruction::f32_const(@lib.F32(Float::from_int(v))))
    (@lib.Instruction::F32ConvertI32U, Some(@lib.I32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(Float::from_uint(v.reinterpret_as_uint())),
        ),
      )
    (@lib.Instruction::F64ConvertI32S, Some(@lib.I32(v))) =>
      return Some(@lib.Instruction::f64_const(@lib.F64(v.to_double())))
    (@lib.Instruction::F64ConvertI32U, Some(@lib.I32(v))) =>
      return Some(
        @lib.Instruction::f64_const(
          @lib.F64(v.reinterpret_as_uint().to_double()),
        ),
      )
    (@lib.Instruction::F32ReinterpretI32, Some(@lib.I32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(Float::reinterpret_from_uint(v.reinterpret_as_uint())),
        ),
      )
    (@lib.Instruction::I32Extend8S, Some(@lib.I32(v))) =>
      return Some(@lib.Instruction::i32_const(@lib.I32(v << 24 >> 24)))
    (@lib.Instruction::I32Extend16S, Some(@lib.I32(v))) =>
      return Some(@lib.Instruction::i32_const(@lib.I32(v << 16 >> 16)))
    _ => ()
  }
  match (instr, precompute_raw_i64_const(operand)) {
    (@lib.Instruction::I64Eqz, Some(v)) =>
      return Some(precompute_raw_bool(v == @lib.I64(0L)))
    (@lib.Instruction::I64Clz, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(v.reinterpret_as_uint64().clz().to_int64()),
        ),
      )
    (@lib.Instruction::I64Ctz, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(v.reinterpret_as_uint64().ctz().to_int64()),
        ),
      )
    (@lib.Instruction::I64Popcnt, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(v.reinterpret_as_uint64().popcnt().to_int64()),
        ),
      )
    (@lib.Instruction::I32WrapI64, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32(v.reinterpret_as_uint64().to_uint().reinterpret_as_int()),
        ),
      )
    (@lib.Instruction::F32ConvertI64S, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            precompute_u64_to_f32(
              if v < 0L {
                0UL - v.reinterpret_as_uint64()
              } else {
                v.reinterpret_as_uint64()
              },
              negative=v < 0L,
            ),
          ),
        ),
      )
    (@lib.Instruction::F32ConvertI64U, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(precompute_u64_to_f32(v.reinterpret_as_uint64())),
        ),
      )
    (@lib.Instruction::F64ConvertI64S, Some(@lib.I64(v))) =>
      return Some(@lib.Instruction::f64_const(@lib.F64(v.to_double())))
    (@lib.Instruction::F64ConvertI64U, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::f64_const(
          @lib.F64(v.reinterpret_as_uint64().to_double()),
        ),
      )
    (@lib.Instruction::F64ReinterpretI64, Some(@lib.I64(v))) =>
      return Some(
        @lib.Instruction::f64_const(
          @lib.F64(v.reinterpret_as_uint64().reinterpret_as_double()),
        ),
      )
    (@lib.Instruction::I64Extend8S, Some(@lib.I64(v))) =>
      return Some(@lib.Instruction::i64_const(@lib.I64(v << 56 >> 56)))
    (@lib.Instruction::I64Extend16S, Some(@lib.I64(v))) =>
      return Some(@lib.Instruction::i64_const(@lib.I64(v << 48 >> 48)))
    (@lib.Instruction::I64Extend32S, Some(@lib.I64(v))) =>
      return Some(@lib.Instruction::i64_const(@lib.I64(v << 32 >> 32)))
    _ => ()
  }
  match (instr, precompute_raw_f32_const(operand)) {
    (@lib.Instruction::F32Abs, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            Float::reinterpret_from_uint(v.reinterpret_as_uint() & 0x7fffffffU),
          ),
        ),
      )
    (@lib.Instruction::F32Neg, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            Float::reinterpret_from_uint(v.reinterpret_as_uint() ^ 0x80000000U),
          ),
        ),
      )
    (@lib.Instruction::F32Ceil, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if v.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              v.ceil()
            },
          ),
        ),
      )
    (@lib.Instruction::F32Floor, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if v.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              v.floor()
            },
          ),
        ),
      )
    (@lib.Instruction::F32Trunc, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if v.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              v.trunc()
            },
          ),
        ),
      )
    (@lib.Instruction::F32Nearest, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if v.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              precompute_raw_f32_nearest(v)
            },
          ),
        ),
      )
    (@lib.Instruction::F32Sqrt, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if v.is_nan() || v < Float::from_double(0.0) {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              v.sqrt()
            },
          ),
        ),
      )
    (@lib.Instruction::F64PromoteF32, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if v.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              v.to_double()
            },
          ),
        ),
      )
    (@lib.Instruction::I32ReinterpretF32, Some(@lib.F32(v))) =>
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32(v.reinterpret_as_uint().reinterpret_as_int()),
        ),
      )
    (@lib.Instruction::I32TruncF32S, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i32_trunc(v.to_double(), true, false)
    (@lib.Instruction::I32TruncF32U, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i32_trunc(v.to_double(), false, false)
    (@lib.Instruction::I64TruncF32S, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i64_trunc(v.to_double(), true, false)
    (@lib.Instruction::I64TruncF32U, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i64_trunc(v.to_double(), false, false)
    (@lib.Instruction::I32TruncSatF32S, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i32_trunc(v.to_double(), true, true)
    (@lib.Instruction::I32TruncSatF32U, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i32_trunc(v.to_double(), false, true)
    (@lib.Instruction::I64TruncSatF32S, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i64_trunc(v.to_double(), true, true)
    (@lib.Instruction::I64TruncSatF32U, Some(@lib.F32(v))) =>
      return precompute_raw_try_fold_i64_trunc(v.to_double(), false, true)
    _ => ()
  }
  match (instr, precompute_raw_f64_const(operand)) {
    (@lib.Instruction::F64Abs, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            (v.reinterpret_as_uint64() & 0x7fffffffffffffffUL).reinterpret_as_double(),
          ),
        ),
      )
    (@lib.Instruction::F64Neg, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            (v.reinterpret_as_uint64() ^ 0x8000000000000000UL).reinterpret_as_double(),
          ),
        ),
      )
    (@lib.Instruction::F64Ceil, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if v.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              v.ceil()
            },
          ),
        ),
      )
    (@lib.Instruction::F64Floor, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if v.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              v.floor()
            },
          ),
        ),
      )
    (@lib.Instruction::F64Trunc, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if v.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              v.trunc()
            },
          ),
        ),
      )
    (@lib.Instruction::F64Nearest, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if v.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              precompute_raw_f64_nearest(v)
            },
          ),
        ),
      )
    (@lib.Instruction::F64Sqrt, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if v.is_nan() || v < 0.0 {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              v.sqrt()
            },
          ),
        ),
      )
    (@lib.Instruction::F32DemoteF64, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if v.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              Float::from_double(v)
            },
          ),
        ),
      )
    (@lib.Instruction::I64ReinterpretF64, Some(@lib.F64(v))) =>
      Some(
        @lib.Instruction::i64_const(
          @lib.I64(v.reinterpret_as_uint64().reinterpret_as_int64()),
        ),
      )
    (@lib.Instruction::I32TruncF64S, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i32_trunc(v, true, false)
    (@lib.Instruction::I32TruncF64U, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i32_trunc(v, false, false)
    (@lib.Instruction::I64TruncF64S, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i64_trunc(v, true, false)
    (@lib.Instruction::I64TruncF64U, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i64_trunc(v, false, false)
    (@lib.Instruction::I32TruncSatF64S, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i32_trunc(v, true, true)
    (@lib.Instruction::I32TruncSatF64U, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i32_trunc(v, false, true)
    (@lib.Instruction::I64TruncSatF64S, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i64_trunc(v, true, true)
    (@lib.Instruction::I64TruncSatF64U, Some(@lib.F64(v))) =>
      precompute_raw_try_fold_i64_trunc(v, false, true)
    _ => None
  }
}

///|
fn precompute_raw_try_fold_binary(
  lhs_instr : @lib.Instruction,
  rhs_instr : @lib.Instruction,
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  match precompute_raw_try_fold_v128_replace_lane(lhs_instr, rhs_instr, instr) {
    Some(folded) => return Some(folded)
    None => ()
  }
  match (instr, lhs_instr, rhs_instr) {
    (@lib.F32x4Eq, _, _) =>
      return precompute_raw_try_fold_f32x4_eq(lhs_instr, rhs_instr)
    (@lib.F32x4Min, _, _) =>
      return precompute_raw_try_fold_f32x4_minmax(
        lhs_instr,
        rhs_instr,
        @lib.Instruction::f32_min(),
      )
    (@lib.F32x4Max, _, _) =>
      return precompute_raw_try_fold_f32x4_minmax(
        lhs_instr,
        rhs_instr,
        @lib.Instruction::f32_max(),
      )
    (@lib.F64x2Min, _, _) =>
      return precompute_raw_try_fold_f64x2_minmax(
        lhs_instr,
        rhs_instr,
        @lib.Instruction::f64_min(),
      )
    (@lib.F64x2Max, _, _) =>
      return precompute_raw_try_fold_f64x2_minmax(
        lhs_instr,
        rhs_instr,
        @lib.Instruction::f64_max(),
      )
    (@lib.F64x2Gt, _, _) =>
      return precompute_raw_try_fold_f64x2_compare(
        lhs_instr,
        rhs_instr,
        @lib.Instruction::f64_gt(),
      )
    (@lib.I64x2GeS, _, _) =>
      return precompute_raw_try_fold_i64x2_ge_s(lhs_instr, rhs_instr)
    (@lib.I8x16Ne, _, _) =>
      return precompute_raw_try_fold_i8x16_ne(lhs_instr, rhs_instr)
    (@lib.V128Xor, _, _) =>
      return precompute_raw_try_fold_v128_xor(lhs_instr, rhs_instr)
    (@lib.I16x8NarrowI32x4U, _, _) =>
      return precompute_raw_try_fold_i16x8_narrow_i32x4_u(lhs_instr, rhs_instr)
    (@lib.F64x2Eq, _, _) =>
      return precompute_raw_try_fold_f64x2_eq(lhs_instr, rhs_instr)
    (@lib.I8x16Shl, _, _) =>
      return precompute_raw_try_fold_i8x16_shl(lhs_instr, rhs_instr)
    (@lib.I8x16ShrS, _, _) =>
      return precompute_raw_try_fold_i8x16_shr_s(lhs_instr, rhs_instr)
    (@lib.StringConcat, @lib.StringConst(lhs), @lib.StringConst(rhs)) => {
      let lhs_units = match precompute_string_bytes_to_utf16(lhs) {
        Some(units) => units
        None => return None
      }
      let rhs_units = match precompute_string_bytes_to_utf16(rhs) {
        Some(units) => units
        None => return None
      }
      let units = lhs_units + rhs_units
      return match
        precompute_utf16_units_to_valid_utf8(units, 0, units.length()) {
        Some(bytes) => Some(@lib.Instruction::string_const(bytes))
        None => None
      }
    }
    (@lib.StringEq, @lib.StringConst(lhs), @lib.StringConst(rhs)) => {
      let lhs_units = match precompute_string_bytes_to_utf16(lhs) {
        Some(units) => units
        None => return None
      }
      let rhs_units = match precompute_string_bytes_to_utf16(rhs) {
        Some(units) => units
        None => return None
      }
      return Some(precompute_raw_bool(lhs_units == rhs_units))
    }
    (
      @lib.StringViewWtf16GetCodeunit,
      @lib.StringConst(bytes),
      @lib.I32Const(@lib.I32(index)),
    ) => {
      let units = match precompute_string_bytes_to_utf16(bytes) {
        Some(units) => units
        None => return None
      }
      if index < 0 || index >= units.length() {
        return None
      }
      return Some(@lib.Instruction::i32_const(@lib.I32(units[index])))
    }
    _ => ()
  }
  match
    (
      instr,
      precompute_raw_i32_const(lhs_instr),
      precompute_raw_i32_const(rhs_instr),
    ) {
    (@lib.Instruction::I32Add, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32(
            (lhs.reinterpret_as_uint() + rhs.reinterpret_as_uint()).reinterpret_as_int(),
          ),
        ),
      )
    (@lib.Instruction::I32Sub, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32(
            (lhs.reinterpret_as_uint() - rhs.reinterpret_as_uint()).reinterpret_as_int(),
          ),
        ),
      )
    (@lib.Instruction::I32Mul, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32(
            (lhs.reinterpret_as_uint() * rhs.reinterpret_as_uint()).reinterpret_as_int(),
          ),
        ),
      )
    (@lib.Instruction::I32And, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(@lib.Instruction::i32_const(@lib.I32(lhs & rhs)))
    (@lib.Instruction::I32Or, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(@lib.Instruction::i32_const(@lib.I32(lhs | rhs)))
    (@lib.Instruction::I32Xor, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(@lib.Instruction::i32_const(@lib.I32(lhs ^ rhs)))
    (@lib.Instruction::I32Shl, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32(
            (lhs.reinterpret_as_uint() << shift.reinterpret_as_int()).reinterpret_as_int(),
          ),
        ),
      )
    }
    (@lib.Instruction::I32ShrU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32(
            (lhs.reinterpret_as_uint() >> shift.reinterpret_as_int()).reinterpret_as_int(),
          ),
        ),
      )
    }
    (@lib.Instruction::I32ShrS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      return Some(
        @lib.Instruction::i32_const(@lib.I32(lhs >> shift.reinterpret_as_int())),
      )
    }
    (@lib.Instruction::I32Rotl, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      let value = lhs.reinterpret_as_uint()
      let rotated = if shift == 0U {
        value
      } else {
        (value << shift.reinterpret_as_int()) |
        (value >> (32 - shift.reinterpret_as_int()))
      }
      return Some(
        @lib.Instruction::i32_const(@lib.I32(rotated.reinterpret_as_int())),
      )
    }
    (@lib.Instruction::I32Rotr, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let shift = rhs.reinterpret_as_uint() & 31U
      let value = lhs.reinterpret_as_uint()
      let rotated = if shift == 0U {
        value
      } else {
        (value >> shift.reinterpret_as_int()) |
        (value << (32 - shift.reinterpret_as_int()))
      }
      return Some(
        @lib.Instruction::i32_const(@lib.I32(rotated.reinterpret_as_int())),
      )
    }
    (@lib.Instruction::I32Eq, Some(lhs), Some(rhs)) =>
      return Some(precompute_raw_bool(lhs == rhs))
    (@lib.Instruction::I32Ne, Some(lhs), Some(rhs)) =>
      return Some(precompute_raw_bool(lhs != rhs))
    (@lib.Instruction::I32LtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(precompute_raw_bool(lhs < rhs))
    (@lib.Instruction::I32LtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint() < rhs.reinterpret_as_uint(),
        ),
      )
    (@lib.Instruction::I32GtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(precompute_raw_bool(lhs > rhs))
    (@lib.Instruction::I32GtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint() > rhs.reinterpret_as_uint(),
        ),
      )
    (@lib.Instruction::I32LeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(precompute_raw_bool(lhs <= rhs))
    (@lib.Instruction::I32LeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint() <= rhs.reinterpret_as_uint(),
        ),
      )
    (@lib.Instruction::I32GeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(precompute_raw_bool(lhs >= rhs))
    (@lib.Instruction::I32GeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint() >= rhs.reinterpret_as_uint(),
        ),
      )
    (@lib.Instruction::I32DivS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      if rhs == 0 || (lhs == -2147483648 && rhs == -1) {
        return None
      }
      return Some(@lib.Instruction::i32_const(@lib.I32(lhs / rhs)))
    }
    (@lib.Instruction::I32DivU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let divisor = rhs.reinterpret_as_uint()
      if divisor == 0U {
        return None
      }
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32((lhs.reinterpret_as_uint() / divisor).reinterpret_as_int()),
        ),
      )
    }
    (@lib.Instruction::I32RemS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      if rhs == 0 {
        return None
      }
      let value = if lhs == -2147483648 && rhs == -1 { 0 } else { lhs % rhs }
      return Some(@lib.Instruction::i32_const(@lib.I32(value)))
    }
    (@lib.Instruction::I32RemU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
      let divisor = rhs.reinterpret_as_uint()
      if divisor == 0U {
        return None
      }
      return Some(
        @lib.Instruction::i32_const(
          @lib.I32((lhs.reinterpret_as_uint() % divisor).reinterpret_as_int()),
        ),
      )
    }
    _ => ()
  }
  match
    (
      instr,
      precompute_raw_i64_const(lhs_instr),
      precompute_raw_i64_const(rhs_instr),
    ) {
    (@lib.Instruction::I64Add, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            (lhs.reinterpret_as_uint64() + rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
          ),
        ),
      )
    (@lib.Instruction::I64Sub, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            (lhs.reinterpret_as_uint64() - rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
          ),
        ),
      )
    (@lib.Instruction::I64Mul, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            (lhs.reinterpret_as_uint64() * rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
          ),
        ),
      )
    (@lib.Instruction::I64And, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(@lib.Instruction::i64_const(@lib.I64(lhs & rhs)))
    (@lib.Instruction::I64Or, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(@lib.Instruction::i64_const(@lib.I64(lhs | rhs)))
    (@lib.Instruction::I64Xor, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(@lib.Instruction::i64_const(@lib.I64(lhs ^ rhs)))
    (@lib.Instruction::I64Shl, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            (lhs.reinterpret_as_uint64() << shift.to_int()).reinterpret_as_int64(),
          ),
        ),
      )
    }
    (@lib.Instruction::I64ShrU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            (lhs.reinterpret_as_uint64() >> shift.to_int()).reinterpret_as_int64(),
          ),
        ),
      )
    }
    (@lib.Instruction::I64ShrS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      return Some(@lib.Instruction::i64_const(@lib.I64(lhs >> shift.to_int())))
    }
    (@lib.Instruction::I64Rotl, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      let value = lhs.reinterpret_as_uint64()
      let rotated = if shift == 0UL {
        value
      } else {
        (value << shift.to_int()) | (value >> (64 - shift.to_int()))
      }
      return Some(
        @lib.Instruction::i64_const(@lib.I64(rotated.reinterpret_as_int64())),
      )
    }
    (@lib.Instruction::I64Rotr, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let shift = rhs.reinterpret_as_uint64() & 63UL
      let value = lhs.reinterpret_as_uint64()
      let rotated = if shift == 0UL {
        value
      } else {
        (value >> shift.to_int()) | (value << (64 - shift.to_int()))
      }
      return Some(
        @lib.Instruction::i64_const(@lib.I64(rotated.reinterpret_as_int64())),
      )
    }
    (@lib.Instruction::I64Eq, Some(lhs), Some(rhs)) =>
      return Some(precompute_raw_bool(lhs == rhs))
    (@lib.Instruction::I64Ne, Some(lhs), Some(rhs)) =>
      return Some(precompute_raw_bool(lhs != rhs))
    (@lib.Instruction::I64LtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(precompute_raw_bool(lhs < rhs))
    (@lib.Instruction::I64LtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint64() < rhs.reinterpret_as_uint64(),
        ),
      )
    (@lib.Instruction::I64GtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(precompute_raw_bool(lhs > rhs))
    (@lib.Instruction::I64GtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint64() > rhs.reinterpret_as_uint64(),
        ),
      )
    (@lib.Instruction::I64LeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(precompute_raw_bool(lhs <= rhs))
    (@lib.Instruction::I64LeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint64() <= rhs.reinterpret_as_uint64(),
        ),
      )
    (@lib.Instruction::I64GeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(precompute_raw_bool(lhs >= rhs))
    (@lib.Instruction::I64GeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
      return Some(
        precompute_raw_bool(
          lhs.reinterpret_as_uint64() >= rhs.reinterpret_as_uint64(),
        ),
      )
    (@lib.Instruction::I64DivS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      if rhs == 0L || (lhs == -9223372036854775808L && rhs == -1L) {
        return None
      }
      return Some(@lib.Instruction::i64_const(@lib.I64(lhs / rhs)))
    }
    (@lib.Instruction::I64DivU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let divisor = rhs.reinterpret_as_uint64()
      if divisor == 0UL {
        return None
      }
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            (lhs.reinterpret_as_uint64() / divisor).reinterpret_as_int64(),
          ),
        ),
      )
    }
    (@lib.Instruction::I64RemS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      if rhs == 0L {
        return None
      }
      let value = if lhs == -9223372036854775808L && rhs == -1L {
        0L
      } else {
        lhs % rhs
      }
      return Some(@lib.Instruction::i64_const(@lib.I64(value)))
    }
    (@lib.Instruction::I64RemU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
      let divisor = rhs.reinterpret_as_uint64()
      if divisor == 0UL {
        return None
      }
      return Some(
        @lib.Instruction::i64_const(
          @lib.I64(
            (lhs.reinterpret_as_uint64() % divisor).reinterpret_as_int64(),
          ),
        ),
      )
    }
    _ => ()
  }
  match
    (
      instr,
      precompute_raw_f32_const(lhs_instr),
      precompute_raw_f32_const(rhs_instr),
    ) {
    (@lib.Instruction::F32Add, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
      let value = lhs + rhs
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if value.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F32Sub, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
      let value = lhs - rhs
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if value.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F32Mul, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
      let value = lhs * rhs
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if value.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F32Div, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
      let value = lhs / rhs
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            if value.is_nan() {
              Float::reinterpret_from_uint(0x7fc00000U)
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F32Min, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
      if lhs.is_nan() || rhs.is_nan() {
        return Some(
          @lib.Instruction::f32_const(
            @lib.F32(Float::reinterpret_from_uint(0x7fc00000U)),
          ),
        )
      }
      let value = if lhs == rhs {
        if lhs == Float::from_double(0.0) &&
          (
            (lhs.reinterpret_as_uint() | rhs.reinterpret_as_uint()) &
            0x80000000U
          ) !=
          0U {
          Float::reinterpret_from_uint(0x80000000U)
        } else {
          lhs
        }
      } else if lhs < rhs {
        lhs
      } else {
        rhs
      }
      return Some(@lib.Instruction::f32_const(@lib.F32(value)))
    }
    (@lib.Instruction::F32Max, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
      if lhs.is_nan() || rhs.is_nan() {
        return Some(
          @lib.Instruction::f32_const(
            @lib.F32(Float::reinterpret_from_uint(0x7fc00000U)),
          ),
        )
      }
      let value = if lhs == rhs {
        if lhs == Float::from_double(0.0) {
          if (
              lhs.reinterpret_as_uint() &
              rhs.reinterpret_as_uint() &
              0x80000000U
            ) !=
            0U {
            Float::reinterpret_from_uint(0x80000000U)
          } else {
            Float::reinterpret_from_uint(0U)
          }
        } else {
          lhs
        }
      } else if lhs > rhs {
        lhs
      } else {
        rhs
      }
      return Some(@lib.Instruction::f32_const(@lib.F32(value)))
    }
    (@lib.Instruction::F32Copysign, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
      return Some(
        @lib.Instruction::f32_const(
          @lib.F32(
            Float::reinterpret_from_uint(
              (lhs.reinterpret_as_uint() & 0x7fffffffU) |
              (rhs.reinterpret_as_uint() & 0x80000000U),
            ),
          ),
        ),
      )
    (@lib.Instruction::F32Eq, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
      return Some(precompute_raw_bool(lhs == rhs))
    (@lib.Instruction::F32Ne, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
      return Some(precompute_raw_bool(lhs != rhs))
    (@lib.Instruction::F32Lt, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
      return Some(precompute_raw_bool(lhs < rhs))
    (@lib.Instruction::F32Gt, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
      return Some(precompute_raw_bool(lhs > rhs))
    (@lib.Instruction::F32Le, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
      return Some(precompute_raw_bool(lhs <= rhs))
    (@lib.Instruction::F32Ge, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
      return Some(precompute_raw_bool(lhs >= rhs))
    _ => ()
  }
  match
    (
      instr,
      precompute_raw_f64_const(lhs_instr),
      precompute_raw_f64_const(rhs_instr),
    ) {
    (@lib.Instruction::F64Add, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
      let value = lhs + rhs
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if value.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F64Sub, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
      let value = lhs - rhs
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if value.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F64Mul, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
      let value = lhs * rhs
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if value.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F64Div, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
      let value = lhs / rhs
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            if value.is_nan() {
              0x7ff8000000000000UL.reinterpret_as_double()
            } else {
              value
            },
          ),
        ),
      )
    }
    (@lib.Instruction::F64Min, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
      if lhs.is_nan() || rhs.is_nan() {
        return Some(
          @lib.Instruction::f64_const(
            @lib.F64(0x7ff8000000000000UL.reinterpret_as_double()),
          ),
        )
      }
      let value = if lhs == rhs {
        if lhs == 0.0 &&
          (
            (lhs.reinterpret_as_uint64() | rhs.reinterpret_as_uint64()) &
            0x8000000000000000UL
          ) !=
          0UL {
          0x8000000000000000UL.reinterpret_as_double()
        } else {
          lhs
        }
      } else if lhs < rhs {
        lhs
      } else {
        rhs
      }
      Some(@lib.Instruction::f64_const(@lib.F64(value)))
    }
    (@lib.Instruction::F64Max, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
      if lhs.is_nan() || rhs.is_nan() {
        return Some(
          @lib.Instruction::f64_const(
            @lib.F64(0x7ff8000000000000UL.reinterpret_as_double()),
          ),
        )
      }
      let value = if lhs == rhs {
        if lhs == 0.0 {
          if (
              lhs.reinterpret_as_uint64() &
              rhs.reinterpret_as_uint64() &
              0x8000000000000000UL
            ) !=
            0UL {
            0x8000000000000000UL.reinterpret_as_double()
          } else {
            0.0
          }
        } else {
          lhs
        }
      } else if lhs > rhs {
        lhs
      } else {
        rhs
      }
      Some(@lib.Instruction::f64_const(@lib.F64(value)))
    }
    (@lib.Instruction::F64Copysign, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
      Some(
        @lib.Instruction::f64_const(
          @lib.F64(
            ((lhs.reinterpret_as_uint64() & 0x7fffffffffffffffUL) |
            (rhs.reinterpret_as_uint64() & 0x8000000000000000UL)).reinterpret_as_double(),
          ),
        ),
      )
    (@lib.Instruction::F64Eq, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
      Some(precompute_raw_bool(lhs == rhs))
    (@lib.Instruction::F64Ne, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
      Some(precompute_raw_bool(lhs != rhs))
    (@lib.Instruction::F64Lt, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
      Some(precompute_raw_bool(lhs < rhs))
    (@lib.Instruction::F64Gt, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
      Some(precompute_raw_bool(lhs > rhs))
    (@lib.Instruction::F64Le, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
      Some(precompute_raw_bool(lhs <= rhs))
    (@lib.Instruction::F64Ge, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
      Some(precompute_raw_bool(lhs >= rhs))
    _ => None
  }
}

///|
fn precompute_raw_instr_is_flat_discardable_value(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.I32Const(_)
    | @lib.I64Const(_)
    | @lib.F32Const(_)
    | @lib.F64Const(_)
    | @lib.LocalGet(_)
    | @lib.GlobalGet(_)
    | @lib.RefNull(_)
    | @lib.RefFunc(_) => true
    _ => false
  }
}

///|
fn precompute_raw_instr_is_nontrapping_unary_op(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.Instruction::I32Eqz
    | @lib.Instruction::I64Eqz
    | @lib.Instruction::I32Clz
    | @lib.Instruction::I32Ctz
    | @lib.Instruction::I32Popcnt
    | @lib.Instruction::I64Clz
    | @lib.Instruction::I64Ctz
    | @lib.Instruction::I64Popcnt
    | @lib.Instruction::F32Abs
    | @lib.Instruction::F32Neg
    | @lib.Instruction::F32Ceil
    | @lib.Instruction::F32Floor
    | @lib.Instruction::F32Trunc
    | @lib.Instruction::F32Nearest
    | @lib.Instruction::F32Sqrt
    | @lib.Instruction::F64Abs
    | @lib.Instruction::F64Neg
    | @lib.Instruction::F64Ceil
    | @lib.Instruction::F64Floor
    | @lib.Instruction::F64Trunc
    | @lib.Instruction::F64Nearest
    | @lib.Instruction::F64Sqrt
    | @lib.Instruction::I32WrapI64
    | @lib.Instruction::I64ExtendI32S
    | @lib.Instruction::I64ExtendI32U
    | @lib.Instruction::F32ConvertI32S
    | @lib.Instruction::F32ConvertI32U
    | @lib.Instruction::F32ConvertI64S
    | @lib.Instruction::F32ConvertI64U
    | @lib.Instruction::F64ConvertI32S
    | @lib.Instruction::F64ConvertI32U
    | @lib.Instruction::F64ConvertI64S
    | @lib.Instruction::F64ConvertI64U
    | @lib.Instruction::F32DemoteF64
    | @lib.Instruction::F64PromoteF32
    | @lib.Instruction::I32ReinterpretF32
    | @lib.Instruction::I64ReinterpretF64
    | @lib.Instruction::F32ReinterpretI32
    | @lib.Instruction::F64ReinterpretI64
    | @lib.Instruction::I32Extend8S
    | @lib.Instruction::I32Extend16S
    | @lib.Instruction::I64Extend8S
    | @lib.Instruction::I64Extend16S
    | @lib.Instruction::I64Extend32S
    | @lib.Instruction::I32TruncSatF32S
    | @lib.Instruction::I32TruncSatF32U
    | @lib.Instruction::I32TruncSatF64S
    | @lib.Instruction::I32TruncSatF64U
    | @lib.Instruction::I64TruncSatF32S
    | @lib.Instruction::I64TruncSatF32U
    | @lib.Instruction::I64TruncSatF64S
    | @lib.Instruction::I64TruncSatF64U
    | @lib.Instruction::RefIsNull
    | @lib.Instruction::AnyConvertExtern
    | @lib.Instruction::ExternConvertAny
    | @lib.Instruction::RefI31 => true
    _ => false
  }
}

///|
fn precompute_raw_instr_is_nontrapping_binary_op(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.Instruction::I32Add
    | @lib.Instruction::I32Sub
    | @lib.Instruction::I32Mul
    | @lib.Instruction::I32And
    | @lib.Instruction::I32Or
    | @lib.Instruction::I32Xor
    | @lib.Instruction::I32Shl
    | @lib.Instruction::I32ShrU
    | @lib.Instruction::I32ShrS
    | @lib.Instruction::I32Eq
    | @lib.Instruction::I32Ne
    | @lib.Instruction::I32LtS
    | @lib.Instruction::I32LtU
    | @lib.Instruction::I32GtS
    | @lib.Instruction::I32GtU
    | @lib.Instruction::I32LeS
    | @lib.Instruction::I32LeU
    | @lib.Instruction::I32GeS
    | @lib.Instruction::I32GeU
    | @lib.Instruction::I64Add
    | @lib.Instruction::I64Sub
    | @lib.Instruction::I64Mul
    | @lib.Instruction::I64And
    | @lib.Instruction::I64Or
    | @lib.Instruction::I64Xor
    | @lib.Instruction::I64Shl
    | @lib.Instruction::I64ShrU
    | @lib.Instruction::I64ShrS
    | @lib.Instruction::I64Eq
    | @lib.Instruction::I64Ne
    | @lib.Instruction::I64LtS
    | @lib.Instruction::I64LtU
    | @lib.Instruction::I64GtS
    | @lib.Instruction::I64GtU
    | @lib.Instruction::I64LeS
    | @lib.Instruction::I64LeU
    | @lib.Instruction::I64GeS
    | @lib.Instruction::I64GeU
    | @lib.Instruction::I32Rotl
    | @lib.Instruction::I32Rotr
    | @lib.Instruction::I64Rotl
    | @lib.Instruction::I64Rotr
    | @lib.Instruction::F32Add
    | @lib.Instruction::F32Sub
    | @lib.Instruction::F32Mul
    | @lib.Instruction::F32Div
    | @lib.Instruction::F32Min
    | @lib.Instruction::F32Max
    | @lib.Instruction::F32Copysign
    | @lib.Instruction::F32Eq
    | @lib.Instruction::F32Ne
    | @lib.Instruction::F32Lt
    | @lib.Instruction::F32Gt
    | @lib.Instruction::F32Le
    | @lib.Instruction::F32Ge
    | @lib.Instruction::F64Add
    | @lib.Instruction::F64Sub
    | @lib.Instruction::F64Mul
    | @lib.Instruction::F64Div
    | @lib.Instruction::F64Min
    | @lib.Instruction::F64Max
    | @lib.Instruction::F64Copysign
    | @lib.Instruction::F64Eq
    | @lib.Instruction::F64Ne
    | @lib.Instruction::F64Lt
    | @lib.Instruction::F64Gt
    | @lib.Instruction::F64Le
    | @lib.Instruction::F64Ge
    | @lib.Instruction::RefEq => true
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_label_relative_branch(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    match instr {
      @lib.Br(_)
      | @lib.BrIf(_)
      | @lib.BrTable(_, _)
      | @lib.BrOnNull(_)
      | @lib.BrOnNonNull(_)
      | @lib.BrOnCast(_, _, _, _)
      | @lib.BrOnCastFail(_, _, _, _)
      | @lib.BrOnCastDescEq(_, _, _)
      | @lib.BrOnCastDescEqFail(_, _, _) => return true
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if precompute_raw_instrs_have_label_relative_branch(body) {
          return true
        }
      @lib.If(_, then_body, else_body) => {
        if precompute_raw_instrs_have_label_relative_branch(then_body) {
          return true
        }
        match else_body {
          Some(body) =>
            if precompute_raw_instrs_have_label_relative_branch(body) {
              return true
            }
          None => ()
        }
      }
      @lib.TryTable(_, catches, @lib.Expr(body)) =>
        if !catches.is_empty() ||
          precompute_raw_instrs_have_label_relative_branch(body) {
          return true
        }
      @lib.Try(_, _, _, _) => return true
      _ => ()
    }
  }
  false
}

///|
fn precompute_raw_label_matches_depth(
  label_idx : @lib.LabelIdx,
  depth : Int32,
) -> Bool {
  let @lib.LabelIdx(raw) = label_idx
  raw.reinterpret_as_int() == depth
}

///|
fn precompute_raw_instr_has_branch_to_depth(
  instr : @lib.Instruction,
  depth : Int32,
) -> Bool {
  match instr {
    @lib.Br(label_idx)
    | @lib.BrIf(label_idx)
    | @lib.BrOnNull(label_idx)
    | @lib.BrOnNonNull(label_idx)
    | @lib.BrOnCast(label_idx, _, _, _)
    | @lib.BrOnCastFail(label_idx, _, _, _)
    | @lib.BrOnCastDescEq(label_idx, _, _)
    | @lib.BrOnCastDescEqFail(label_idx, _, _) =>
      precompute_raw_label_matches_depth(label_idx, depth)
    @lib.BrTable(labels, fallback) => {
      for label_idx in labels {
        if precompute_raw_label_matches_depth(label_idx, depth) {
          return true
        }
      }
      precompute_raw_label_matches_depth(fallback, depth)
    }
    @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
      precompute_raw_instrs_have_branch_to_depth(body, depth + 1)
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_branch_to_depth(then_body, depth + 1) {
        return true
      }
      match else_body {
        Some(body) =>
          precompute_raw_instrs_have_branch_to_depth(body, depth + 1)
        None => false
      }
    }
    @lib.TryTable(_, _, @lib.Expr(_)) => true
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_branch_to_depth(
  instrs : Array[@lib.Instruction],
  depth : Int32,
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_branch_to_depth(instr, depth) {
      return true
    }
  }
  false
}

///|
fn precompute_raw_instrs_have_branch_to_current_label(
  instrs : Array[@lib.Instruction],
) -> Bool {
  precompute_raw_instrs_have_branch_to_depth(instrs, 0)
}

///|
fn precompute_raw_try_fold_const_if_tail(
  out : Array[@lib.Instruction],
  module_ctx : @ir.HotModuleContext?,
  label_arities : Array[Int32?],
) -> Bool {
  if out.length() < 2 {
    return false
  }
  let cond = match precompute_raw_i32_const(out[out.length() - 2]) {
    Some(value) => value
    None => return false
  }
  let (block_type, selected) = match out[out.length() - 1] {
    @lib.If(block_type, then_body, else_body) =>
      (
        block_type,
        if cond == @lib.I32(0) {
          match else_body {
            Some(body) => body
            None => []
          }
        } else {
          then_body
        },
      )
    _ => return false
  }
  ignore(out.pop())
  ignore(out.pop())
  if precompute_raw_instrs_have_label_relative_branch(selected) {
    match precompute_raw_block_terminal_branch_depth(selected) {
      Some(depth) => {
        if !precompute_raw_block_can_flatten_terminal(block_type, module_ctx) {
          out.push(
            @lib.Instruction::block(block_type, @lib.Expr::new(selected.copy())),
          )
          return true
        }
        let selected_label_arities = precompute_raw_append_label_arity(
          label_arities,
          precompute_raw_block_label_arity(block_type, module_ctx),
        )
        let payload_arity = match
          precompute_raw_branch_target_arity(selected_label_arities, depth) {
          Some(arity) => arity
          None => {
            out.push(
              @lib.Instruction::block(
                block_type,
                @lib.Expr::new(selected.copy()),
              ),
            )
            return true
          }
        }
        let prefix = match
          precompute_raw_terminal_branch_prefix(selected, payload_arity) {
          Some(prefix) => prefix
          None => {
            out.push(
              @lib.Instruction::block(
                block_type,
                @lib.Expr::new(selected.copy()),
              ),
            )
            return true
          }
        }
        for instr in prefix {
          out.push(instr)
        }
        if depth > 0U {
          out.push(@lib.Instruction::br(@lib.LabelIdx::new(depth - 1U)))
        }
      }
      None =>
        out.push(
          @lib.Instruction::block(block_type, @lib.Expr::new(selected.copy())),
        )
    }
  } else {
    for instr in selected {
      out.push(instr)
    }
  }
  true
}

///|
fn precompute_raw_try_void_dropped_result_block(
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  let body = match instr {
    @lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) => body
    _ => return None
  }
  if precompute_raw_instrs_have_branch_to_current_label(body) {
    return None
  }
  let void_body = body.copy()
  void_body.push(@lib.Instruction::drop())
  let mut changed = false
  while precompute_raw_fold_output_tail(void_body) {
    changed = true
  }
  if !changed || precompute_raw_instrs_have_drop_needing_hot(void_body) {
    return None
  }
  Some(
    @lib.Instruction::block(@lib.BlockType::void_(), @lib.Expr::new(void_body)),
  )
}

///|
fn precompute_raw_loop_is_unconditional_self_branch(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.Loop(block_type, @lib.Expr([@lib.Br(@lib.LabelIdx(0U))])) =>
      block_type == @lib.BlockType::void_()
    _ => false
  }
}

///|
fn precompute_raw_try_mark_value_tail_after_infinite_loop(
  out : Array[@lib.Instruction],
  visits~ : Array[Int32]? = None,
) -> Bool {
  if out.length() < 2 {
    return false
  }
  let mut idx = out.length() - 1
  let mut has_value_tail = false
  // Only the first non-value/non-nop instruction can be the loop: any
  // earlier loop would have this instruction in its unsupported suffix.
  while idx >= 0 {
    match visits {
      Some(count) => count[0] += 1
      None => ()
    }
    match out[idx] {
      @lib.Nop => ()
      instr if precompute_raw_instr_is_flat_discardable_value(instr) =>
        has_value_tail = true
      instr => {
        if !has_value_tail ||
          !precompute_raw_loop_is_unconditional_self_branch(instr) {
          return false
        }
        while out.length() > idx + 1 {
          ignore(out.pop())
        }
        out.push(@lib.Instruction::unreachable_())
        return true
      }
    }
    idx -= 1
  }
  false
}

///|
fn precompute_raw_instrs_have_partial_heap_select(
  instrs : Array[@lib.Instruction],
) -> Bool {
  let mut saw_select = false
  let mut saw_heap_parent = false
  for instr in instrs {
    match instr {
      @lib.Select(_) => saw_select = true
      @lib.StructGet(_, _)
      | @lib.StructGetS(_, _)
      | @lib.StructGetU(_, _)
      | @lib.RefIsNull
      | @lib.RefEq => saw_heap_parent = true
      _ => ()
    }
  }
  saw_select && saw_heap_parent
}

///|
fn precompute_raw_try_partial_select_condition_tail(
  out : Array[@lib.Instruction],
) -> Bool {
  if out.length() < 7 {
    return false
  }
  let base = out.length() - 7
  let outer_select = match out[base + 6] {
    @lib.Select(_) => out[base + 6]
    _ => return false
  }
  match out[base + 5] {
    @lib.Select(_) => ()
    _ => return false
  }
  let condition_true = match out[base + 2] {
    @lib.I32Const(@lib.I32(value)) => value != 0
    _ => return false
  }
  let condition_false = match out[base + 3] {
    @lib.I32Const(@lib.I32(value)) => value != 0
    _ => return false
  }
  if !precompute_raw_instr_is_flat_discardable_value(out[base + 4]) {
    return false
  }
  let outer_true = out[base]
  let outer_false = out[base + 1]
  if !precompute_raw_is_literal_const(outer_true) ||
    !precompute_raw_is_literal_const(outer_false) {
    return false
  }
  let next_true = if condition_true { outer_true } else { outer_false }
  let next_false = if condition_false { outer_true } else { outer_false }
  let condition = out[base + 4]
  while out.length() > base {
    ignore(out.pop())
  }
  out.push(next_true)
  out.push(next_false)
  out.push(condition)
  out.push(outer_select)
  true
}

///|
fn precompute_raw_try_partial_select_unary_tail(
  out : Array[@lib.Instruction],
) -> Bool {
  if out.length() < 5 {
    return false
  }
  let base = out.length() - 5
  let select_instr = match out[base + 3] {
    @lib.Select(_) => out[base + 3]
    _ => return false
  }
  if !precompute_raw_instr_is_flat_discardable_value(out[base + 2]) {
    return false
  }
  let folded_true = match
    precompute_raw_try_fold_unary(out[base], out[base + 4]) {
    Some(value) => value
    None => return false
  }
  let folded_false = match
    precompute_raw_try_fold_unary(out[base + 1], out[base + 4]) {
    Some(value) => value
    None => return false
  }
  let condition = out[base + 2]
  while out.length() > base {
    ignore(out.pop())
  }
  out.push(folded_true)
  out.push(folded_false)
  out.push(condition)
  out.push(select_instr)
  true
}

///|
fn precompute_raw_try_partial_select_tail(
  out : Array[@lib.Instruction],
) -> Bool {
  if out.length() < 6 {
    return false
  }
  let base = out.length() - 6
  let op = out[base + 5]
  let select_lhs = match out[base + 3] {
    @lib.Select(_) => true
    _ => false
  }
  let select_rhs = match out[base + 4] {
    @lib.Select(_) => true
    _ => false
  }
  let (true_value, false_value, condition, select_instr, other, select_is_lhs) = if select_lhs {
    (
      out[base],
      out[base + 1],
      out[base + 2],
      out[base + 3],
      out[base + 4],
      true,
    )
  } else if select_rhs {
    (
      out[base + 1],
      out[base + 2],
      out[base + 3],
      out[base + 4],
      out[base],
      false,
    )
  } else {
    return false
  }
  if !precompute_raw_instr_is_flat_discardable_value(condition) {
    return false
  }
  let folded_true = match
    (if select_is_lhs {
      precompute_raw_try_fold_binary(true_value, other, op)
    } else {
      precompute_raw_try_fold_binary(other, true_value, op)
    }) {
    Some(value) => value
    None => return false
  }
  let folded_false = match
    (if select_is_lhs {
      precompute_raw_try_fold_binary(false_value, other, op)
    } else {
      precompute_raw_try_fold_binary(other, false_value, op)
    }) {
    Some(value) => value
    None => return false
  }
  while out.length() > base {
    ignore(out.pop())
  }
  out.push(folded_true)
  out.push(folded_false)
  out.push(condition)
  out.push(select_instr)
  true
}

///|
fn precompute_raw_is_literal_const(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.I32Const(_)
    | @lib.I64Const(_)
    | @lib.F32Const(_)
    | @lib.F64Const(_)
    | @lib.RefNull(_)
    | @lib.StringConst(_) => true
    _ => false
  }
}

///|
fn precompute_raw_is_single_result_branch_literal(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.I32Const(_)
    | @lib.I64Const(_)
    | @lib.F32Const(_)
    | @lib.F64Const(_)
    | @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
    | @lib.RefNull(_)
    | @lib.StringConst(_) => true
    _ => false
  }
}

///|
fn precompute_raw_fold_same_target_br_table_tail(
  out : Array[@lib.Instruction],
  label_arities : Array[Int32?],
) -> Bool {
  // Keep the payload in place and erase only an effect-free, nontrapping
  // literal selector. Requiring a one-result target excludes type-indexed
  // multivalue stacks, while equal table targets make the selected index
  // irrelevant. The ordinary terminal-block flattener preserves any ordered
  // effect or possible trap before this three-instruction suffix.
  if out.length() < 3 ||
    !precompute_raw_is_single_result_branch_literal(out[out.length() - 3]) {
    return false
  }
  match out[out.length() - 2] {
    @lib.I32Const(_) => ()
    _ => return false
  }
  let (targets, fallback) = match out[out.length() - 1] {
    @lib.BrTable(targets, fallback) => (targets, fallback)
    _ => return false
  }
  let @lib.LabelIdx(fallback_depth) = fallback
  if precompute_raw_branch_target_arity(label_arities, fallback_depth) !=
    Some(1) {
    return false
  }
  for target in targets {
    if target != fallback {
      return false
    }
  }
  ignore(out.pop())
  ignore(out.pop())
  out.push(@lib.Instruction::br(fallback))
  true
}

///|
fn precompute_raw_effectful_block_const(
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  let body = match instr {
    @lib.Block(_, @lib.Expr(body)) => body
    _ => return None
  }
  if body.length() < 3 ||
    !precompute_raw_is_literal_const(body[body.length() - 1]) {
    return None
  }
  let mut slot = 0
  while slot + 1 < body.length() {
    if !precompute_raw_is_literal_const(body[slot]) {
      return None
    }
    match body[slot + 1] {
      @lib.LocalSet(_) | @lib.GlobalSet(_) => ()
      _ => return None
    }
    slot = slot + 2
  }
  if slot != body.length() - 1 {
    return None
  }
  Some(body[body.length() - 1])
}

///|
fn precompute_string_bytes_to_utf16(bytes : Bytes) -> Array[Int32]? {
  let units : Array[Int32] = []
  let mut i = 0
  while i < bytes.length() {
    let first = bytes[i].to_int()
    if first <= 0x7f {
      units.push(first)
      i = i + 1
      continue
    }
    if first >= 0xc2 && first <= 0xdf {
      if i + 1 >= bytes.length() {
        return None
      }
      let second = bytes[i + 1].to_int()
      if second < 0x80 || second > 0xbf {
        return None
      }
      units.push(((first & 0x1f) << 6) | (second & 0x3f))
      i = i + 2
      continue
    }
    if first >= 0xe0 && first <= 0xef {
      if i + 2 >= bytes.length() {
        return None
      }
      let second = bytes[i + 1].to_int()
      let third = bytes[i + 2].to_int()
      if second < 0x80 ||
        second > 0xbf ||
        third < 0x80 ||
        third > 0xbf ||
        (first == 0xe0 && second < 0xa0) {
        return None
      }
      units.push(
        ((first & 0x0f) << 12) | ((second & 0x3f) << 6) | (third & 0x3f),
      )
      i = i + 3
      continue
    }
    if first >= 0xf0 && first <= 0xf4 {
      if i + 3 >= bytes.length() {
        return None
      }
      let second = bytes[i + 1].to_int()
      let third = bytes[i + 2].to_int()
      let fourth = bytes[i + 3].to_int()
      if second < 0x80 ||
        second > 0xbf ||
        third < 0x80 ||
        third > 0xbf ||
        fourth < 0x80 ||
        fourth > 0xbf ||
        (first == 0xf0 && second < 0x90) ||
        (first == 0xf4 && second > 0x8f) {
        return None
      }
      let scalar = ((first & 0x07) << 18) |
        ((second & 0x3f) << 12) |
        ((third & 0x3f) << 6) |
        (fourth & 0x3f)
      let offset = scalar - 0x10000
      units.push(0xd800 | (offset >> 10))
      units.push(0xdc00 | (offset & 0x3ff))
      i = i + 4
      continue
    }
    return None
  }
  Some(units)
}

///|
fn precompute_utf16_units_to_valid_utf8(
  units : Array[Int32],
  start : Int32,
  length : Int32,
) -> Bytes? {
  if start < 0 ||
    length < 0 ||
    start > units.length() ||
    length > units.length() - start {
    return None
  }
  let bytes = @buffer.new(size_hint=length * 3)
  let end = start + length
  let mut slot = start
  while slot < end {
    let unit = units[slot] & 0xffff
    if unit >= 0xd800 && unit <= 0xdbff {
      if slot + 1 >= end {
        return None
      }
      let low = units[slot + 1] & 0xffff
      if low < 0xdc00 || low > 0xdfff {
        return None
      }
      let scalar = 0x10000 + ((unit - 0xd800) << 10) + (low - 0xdc00)
      precompute_write_utf8_scalar(bytes, scalar)
      slot = slot + 2
    } else if unit >= 0xdc00 && unit <= 0xdfff {
      return None
    } else {
      precompute_write_utf8_scalar(bytes, unit)
      slot = slot + 1
    }
  }
  Some(bytes.to_bytes())
}

///|
fn precompute_write_utf8_scalar(buf : @buffer.Buffer, scalar : Int32) -> Unit {
  if scalar <= 0x7f {
    buf.write_byte(scalar.to_byte())
  } else if scalar <= 0x7ff {
    buf.write_byte((0xc0 | (scalar >> 6)).to_byte())
    buf.write_byte((0x80 | (scalar & 0x3f)).to_byte())
  } else if scalar <= 0xffff {
    buf.write_byte((0xe0 | (scalar >> 12)).to_byte())
    buf.write_byte((0x80 | ((scalar >> 6) & 0x3f)).to_byte())
    buf.write_byte((0x80 | (scalar & 0x3f)).to_byte())
  } else {
    buf.write_byte((0xf0 | (scalar >> 18)).to_byte())
    buf.write_byte((0x80 | ((scalar >> 12) & 0x3f)).to_byte())
    buf.write_byte((0x80 | ((scalar >> 6) & 0x3f)).to_byte())
    buf.write_byte((0x80 | (scalar & 0x3f)).to_byte())
  }
}

///|
fn precompute_raw_try_fold_string_new_wtf16_tail(
  out : Array[@lib.Instruction],
) -> Bool {
  if out.length() < 4 ||
    out[out.length() - 1] != @lib.Instruction::string_new_wtf16_array() {
    return false
  }
  let (start, end) = match (out[out.length() - 3], out[out.length() - 2]) {
    (@lib.I32Const(@lib.I32(start)), @lib.I32Const(@lib.I32(end))) =>
      (start, end)
    _ => return false
  }
  if start < 0 || end < start {
    return false
  }
  let count = match out[out.length() - 4] {
    @lib.ArrayNewFixed(_, @lib.U32(count)) => count.reinterpret_as_int()
    _ => return false
  }
  let value_start = out.length() - 4 - count
  if value_start < 0 || start > count || end > count {
    return false
  }
  let units : Array[Int32] = []
  for slot = 0; slot < count; slot = slot + 1 {
    match out[value_start + slot] {
      @lib.I32Const(@lib.I32(value)) => units.push(value & 0xffff)
      _ => return false
    }
  }
  let bytes = @buffer.new(size_hint=(end - start) * 3)
  let mut slot = start
  while slot < end {
    let unit = units[slot]
    if unit >= 0xd800 && unit <= 0xdbff {
      if slot + 1 >= end {
        return false
      }
      let low = units[slot + 1]
      if low < 0xdc00 || low > 0xdfff {
        return false
      }
      let scalar = 0x10000 + ((unit - 0xd800) << 10) + (low - 0xdc00)
      precompute_write_utf8_scalar(bytes, scalar)
      slot = slot + 2
    } else if unit >= 0xdc00 && unit <= 0xdfff {
      return false
    } else {
      precompute_write_utf8_scalar(bytes, unit)
      slot = slot + 1
    }
  }
  for pop_idx = 0; pop_idx < count + 4; pop_idx = pop_idx + 1 {
    ignore(pop_idx)
    ignore(out.pop())
  }
  out.push(@lib.Instruction::string_const(bytes.to_bytes()))
  true
}

///|
fn precompute_raw_try_fold_string_slice_tail(
  out : Array[@lib.Instruction],
) -> Bool {
  if out.length() < 4 {
    return false
  }
  let base = out.length() - 4
  let (bytes, start, end) = match
    (out[base], out[base + 1], out[base + 2], out[base + 3]) {
    (
      @lib.StringConst(bytes),
      @lib.I32Const(@lib.I32(start)),
      @lib.I32Const(@lib.I32(end)),
      @lib.StringViewWtf16Slice,
    ) => (bytes, start, end)
    _ => return false
  }
  let units = match precompute_string_bytes_to_utf16(bytes) {
    Some(units) => units
    None => return false
  }
  if start < 0 || end < start || end > units.length() {
    return false
  }
  let sliced = match
    precompute_utf16_units_to_valid_utf8(units, start, end - start) {
    Some(bytes) => bytes
    None => return false
  }
  for pop_idx = 0; pop_idx < 4; pop_idx = pop_idx + 1 {
    ignore(pop_idx)
    ignore(out.pop())
  }
  out.push(@lib.Instruction::string_const(sliced))
  true
}

///|
fn precompute_raw_fold_output_tail(
  out : Array[@lib.Instruction],
  module_ctx~ : @ir.HotModuleContext? = None,
  label_arities~ : Array[Int32?] = [],
  full_tail_checks~ : Array[Int32]? = None,
  loop_tail_visits~ : Array[Int32]? = None,
) -> Bool {
  // The other tail folds end in an operator, control, or write. Ordinary
  // reads and scalar literals can only trigger infinite-loop tail cleanup.
  match out.last() {
    None => return false
    Some(@lib.Nop)
    | Some(@lib.LocalGet(_))
    | Some(@lib.GlobalGet(_))
    | Some(@lib.I32Const(_))
    | Some(@lib.I64Const(_))
    | Some(@lib.F32Const(_))
    | Some(@lib.F64Const(_)) =>
      return precompute_raw_try_mark_value_tail_after_infinite_loop(
        out,
        visits=loop_tail_visits,
      )
    _ => ()
  }
  match full_tail_checks {
    Some(checks) => checks[0] += 1
    None => ()
  }
  if precompute_raw_fold_same_target_br_table_tail(out, label_arities) {
    return true
  }
  if out.length() >= 4 {
    let base = out.length() - 4
    match (out[base], out[base + 1], out[base + 2], out[base + 3]) {
      (
        @lib.I32Const(@lib.I32(0))
        | @lib.I64Const(@lib.I64(0L)),
        @lib.I32Const(@lib.I32(0)),
        @lib.I32Const(@lib.I32(0)),
        @lib.MemoryInit(_, _),
      ) => {
        for _ in 0..<4 {
          ignore(out.pop())
        }
        return true
      }
      _ => ()
    }
  }
  if out.length() >= 12 {
    let base = out.length() - 12
    match
      (
        out[base],
        out[base + 1],
        out[base + 2],
        out[base + 3],
        out[base + 4],
        out[base + 5],
        out[base + 6],
        out[base + 7],
        out[base + 8],
        out[base + 9],
        out[base + 10],
        out[base + 11],
      ) {
      (
        first_value,
        @lib.LocalSet(first_local),
        second_value,
        @lib.LocalSet(second_local),
        @lib.LocalGet(first_compare_get),
        @lib.LocalGet(second_compare_get),
        compare_op,
        @lib.LocalSet(mask_local),
        @lib.LocalGet(first_select_get),
        @lib.LocalGet(second_select_get),
        @lib.LocalGet(mask_get),
        @lib.V128Bitselect,
      ) if first_local == first_compare_get &&
        first_compare_get == second_compare_get &&
        first_local == first_select_get &&
        second_local == second_select_get &&
        mask_local == mask_get &&
        first_local != second_local &&
        first_local != mask_local &&
        second_local != mask_local =>
        match
          precompute_raw_try_fold_binary(first_value, first_value, compare_op) {
          Some(mask) =>
            match
              precompute_raw_try_fold_ternary(
                first_value,
                second_value,
                mask,
                @lib.Instruction::v128_bitselect(),
              ) {
              Some(folded) => {
                for pop_idx = 0; pop_idx < 12; pop_idx = pop_idx + 1 {
                  ignore(pop_idx)
                  ignore(out.pop())
                }
                out.push(first_value)
                out.push(@lib.Instruction::local_set(first_local))
                out.push(second_value)
                out.push(@lib.Instruction::local_set(second_local))
                out.push(mask)
                out.push(@lib.Instruction::local_set(mask_local))
                out.push(folded)
                return true
              }
              None => ()
            }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 7 {
    let base = out.length() - 7
    match
      (
        out[base],
        out[base + 1],
        out[base + 2],
        out[base + 3],
        out[base + 4],
        out[base + 5],
        out[base + 6],
      ) {
      (
        first_value,
        @lib.LocalTee(tee_local),
        second_value,
        @lib.LocalGet(first_get),
        @lib.LocalGet(second_get),
        compare_op,
        @lib.V128Bitselect,
      ) if tee_local == first_get && first_get == second_get =>
        match
          precompute_raw_try_fold_binary(first_value, first_value, compare_op) {
          Some(mask) =>
            match
              precompute_raw_try_fold_ternary(
                first_value,
                second_value,
                mask,
                @lib.Instruction::v128_bitselect(),
              ) {
              Some(folded) => {
                for pop_idx = 0; pop_idx < 7; pop_idx = pop_idx + 1 {
                  ignore(pop_idx)
                  ignore(out.pop())
                }
                out.push(first_value)
                out.push(@lib.Instruction::local_set(tee_local))
                out.push(folded)
                return true
              }
              None => ()
            }
          None => ()
        }
      _ => ()
    }
  }
  if precompute_raw_try_fold_string_slice_tail(out) {
    return true
  }
  if precompute_raw_try_fold_string_new_wtf16_tail(out) {
    return true
  }
  if precompute_raw_try_fold_const_if_tail(out, module_ctx, label_arities) {
    return true
  }
  if precompute_raw_try_partial_select_condition_tail(out) ||
    precompute_raw_try_partial_select_unary_tail(out) ||
    precompute_raw_try_partial_select_tail(out) {
    return true
  }
  if precompute_raw_try_mark_value_tail_after_infinite_loop(
      out,
      visits=loop_tail_visits,
    ) {
    return true
  }
  if out.length() >= 6 {
    let base = out.length() - 6
    match
      (
        out[base],
        out[base + 1],
        out[base + 2],
        out[base + 3],
        out[base + 4],
        out[base + 5],
      ) {
      (
        @lib.I32Const(_) as scratch_const,
        @lib.LocalSet(scratch_local),
        @lib.LocalGet(accumulator_local),
        @lib.LocalGet(read_scratch),
        @lib.I32Add,
        @lib.LocalTee(final_local),
      )
      | (
        @lib.I32Const(_) as scratch_const,
        @lib.LocalSet(scratch_local),
        @lib.LocalGet(accumulator_local),
        @lib.LocalGet(read_scratch),
        @lib.I32Add,
        @lib.LocalSet(final_local),
      ) if scratch_local == read_scratch &&
        accumulator_local == final_local &&
        scratch_local != accumulator_local => {
        // Later reads can still need the scratch write. Replace only this read.
        out[base + 3] = scratch_const
        return true
      }
      _ => ()
    }
  }
  if out.length() >= 7 {
    let base = out.length() - 7
    match
      (
        out[base],
        out[base + 1],
        out[base + 2],
        out[base + 3],
        out[base + 4],
        out[base + 5],
        out[base + 6],
      ) {
      (
        @lib.LocalGet(first_get),
        @lib.I32Const(_) as first_const,
        @lib.I32Add,
        @lib.LocalTee(first_tee),
        @lib.I32Const(_) as second_const,
        @lib.I32Add,
        @lib.LocalTee(final_local) as final_write,
      )
      | (
        @lib.LocalGet(first_get),
        @lib.I32Const(_) as first_const,
        @lib.I32Add,
        @lib.LocalTee(first_tee),
        @lib.I32Const(_) as second_const,
        @lib.I32Add,
        @lib.LocalSet(final_local) as final_write,
      ) if first_get == first_tee && first_tee == final_local =>
        match
          precompute_raw_try_fold_binary(
            first_const,
            second_const,
            @lib.Instruction::i32_add(),
          ) {
          Some(folded_const) => {
            for _ in 0..<7 {
              ignore(out.pop())
            }
            out.push(@lib.Instruction::local_get(first_get))
            out.push(folded_const)
            out.push(@lib.Instruction::i32_add())
            out.push(final_write)
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 8 {
    let base = out.length() - 8
    match
      (
        out[base],
        out[base + 1],
        out[base + 2],
        out[base + 3],
        out[base + 4],
        out[base + 5],
        out[base + 6],
        out[base + 7],
      ) {
      (
        @lib.LocalGet(first_get),
        @lib.I32Const(_) as first_const,
        @lib.I32Add,
        @lib.LocalSet(first_set),
        @lib.LocalGet(second_get),
        @lib.I32Const(_) as second_const,
        @lib.I32Add,
        @lib.LocalSet(second_set),
      ) if first_get == first_set &&
        first_set == second_get &&
        second_get == second_set =>
        match
          precompute_raw_try_fold_binary(
            first_const,
            second_const,
            @lib.Instruction::i32_add(),
          ) {
          Some(folded_const) => {
            for _ in 0..<8 {
              ignore(out.pop())
            }
            out.push(@lib.Instruction::local_get(first_get))
            out.push(folded_const)
            out.push(@lib.Instruction::i32_add())
            out.push(@lib.Instruction::local_set(first_set))
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 5 {
    match out[out.length() - 4] {
      @lib.LocalTee(local_idx) =>
        match
          precompute_raw_try_fold_ternary(
            out[out.length() - 5],
            out[out.length() - 3],
            out[out.length() - 2],
            out[out.length() - 1],
          ) {
          Some(folded) => {
            let value = out[out.length() - 5]
            for pop_idx = 0; pop_idx < 5; pop_idx = pop_idx + 1 {
              ignore(pop_idx)
              ignore(out.pop())
            }
            out.push(value)
            out.push(@lib.Instruction::local_set(local_idx))
            out.push(folded)
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 4 {
    match
      precompute_raw_try_fold_ternary(
        out[out.length() - 4],
        out[out.length() - 3],
        out[out.length() - 2],
        out[out.length() - 1],
      ) {
      Some(folded) => {
        for pop_idx = 0; pop_idx < 4; pop_idx = pop_idx + 1 {
          ignore(pop_idx)
          ignore(out.pop())
        }
        out.push(folded)
        return true
      }
      None => ()
    }
  }
  if out.length() >= 2 {
    match (out[out.length() - 2], out[out.length() - 1]) {
      (@lib.I32Const(@lib.I32(value)), @lib.BrIf(label_idx)) => {
        ignore(out.pop())
        ignore(out.pop())
        if value != 0 {
          out.push(@lib.Instruction::br(label_idx))
        }
        return true
      }
      _ => ()
    }
    match out[out.length() - 1] {
      @lib.Drop => {
        match
          precompute_raw_try_void_dropped_result_block(out[out.length() - 2]) {
          Some(rewritten) => {
            ignore(out.pop())
            ignore(out.pop())
            out.push(rewritten)
            return true
          }
          None => ()
        }
        if precompute_raw_instr_is_flat_discardable_value(out[out.length() - 2]) {
          ignore(out.pop())
          ignore(out.pop())
          out.push(@lib.Instruction::nop())
          return true
        }
        if out.length() >= 3 &&
          dae_instr_is_const_safe_float_to_int_unary(
            out[out.length() - 3],
            out[out.length() - 2],
          ) {
          ignore(out.pop())
          ignore(out.pop())
          ignore(out.pop())
          out.push(@lib.Instruction::nop())
          return true
        }
        if out.length() >= 3 &&
          precompute_raw_instr_is_nontrapping_unary_op(out[out.length() - 2]) &&
          precompute_raw_instr_is_flat_discardable_value(out[out.length() - 3]) {
          ignore(out.pop())
          ignore(out.pop())
          ignore(out.pop())
          out.push(@lib.Instruction::nop())
          return true
        }
        if out.length() >= 4 &&
          precompute_raw_instr_is_nontrapping_binary_op(out[out.length() - 2]) &&
          precompute_raw_instr_is_flat_discardable_value(out[out.length() - 3]) &&
          precompute_raw_instr_is_flat_discardable_value(out[out.length() - 4]) {
          ignore(out.pop())
          ignore(out.pop())
          ignore(out.pop())
          ignore(out.pop())
          out.push(@lib.Instruction::nop())
          return true
        }
        if out.length() >= 5 &&
          precompute_raw_instr_is_flat_discardable_value(out[out.length() - 3]) &&
          precompute_raw_instr_is_flat_discardable_value(out[out.length() - 4]) &&
          precompute_raw_instr_is_flat_discardable_value(out[out.length() - 5]) {
          match out[out.length() - 2] {
            @lib.Select(_) => {
              ignore(out.pop())
              ignore(out.pop())
              ignore(out.pop())
              ignore(out.pop())
              ignore(out.pop())
              out.push(@lib.Instruction::nop())
              return true
            }
            _ => ()
          }
        }
      }
      _ => ()
    }
    match
      precompute_raw_try_fold_unary(
        out[out.length() - 2],
        out[out.length() - 1],
      ) {
      Some(folded) => {
        ignore(out.pop())
        ignore(out.pop())
        out.push(folded)
        return true
      }
      None => ()
    }
  }
  if out.length() >= 4 {
    match (out[out.length() - 3], out[out.length() - 2]) {
      (@lib.LocalTee(tee_local), @lib.LocalGet(get_local)) if tee_local ==
        get_local =>
        match
          precompute_raw_try_fold_binary(
            out[out.length() - 4],
            out[out.length() - 4],
            out[out.length() - 1],
          ) {
          Some(folded) => {
            let value = out[out.length() - 4]
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            out.push(value)
            out.push(@lib.Instruction::local_set(tee_local))
            out.push(folded)
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 5 {
    match (out[out.length() - 4], out[out.length() - 2]) {
      (@lib.LocalTee(first_local), @lib.LocalTee(second_local)) =>
        match
          precompute_raw_try_fold_binary(
            out[out.length() - 5],
            out[out.length() - 3],
            out[out.length() - 1],
          ) {
          Some(folded) => {
            let first_value = out[out.length() - 5]
            let second_value = out[out.length() - 3]
            for i = 0; i < 5; i = i + 1 {
              ignore(i)
              ignore(out.pop())
            }
            out.push(first_value)
            out.push(@lib.Instruction::local_set(first_local))
            out.push(second_value)
            out.push(@lib.Instruction::local_set(second_local))
            out.push(folded)
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 3 {
    match
      (
        precompute_raw_effectful_block_const(out[out.length() - 3]),
        precompute_raw_effectful_block_const(out[out.length() - 2]),
      ) {
      (Some(lhs), Some(rhs)) =>
        match precompute_raw_try_fold_binary(lhs, rhs, out[out.length() - 1]) {
          Some(folded) => {
            let lhs_block = out[out.length() - 3]
            let rhs_block = out[out.length() - 2]
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            out.push(lhs_block)
            out.push(@lib.Instruction::drop())
            out.push(rhs_block)
            out.push(@lib.Instruction::drop())
            out.push(folded)
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 3 {
    match out[out.length() - 2] {
      @lib.LocalTee(local_idx) =>
        match
          precompute_raw_try_fold_unary(
            out[out.length() - 3],
            out[out.length() - 1],
          ) {
          Some(folded) => {
            let value = out[out.length() - 3]
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            out.push(value)
            out.push(@lib.Instruction::local_set(local_idx))
            out.push(folded)
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 4 {
    match out[out.length() - 3] {
      @lib.LocalTee(local_idx) =>
        match
          precompute_raw_try_fold_binary(
            out[out.length() - 4],
            out[out.length() - 2],
            out[out.length() - 1],
          ) {
          Some(folded) => {
            let value = out[out.length() - 4]
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            out.push(value)
            out.push(@lib.Instruction::local_set(local_idx))
            out.push(folded)
            return true
          }
          None => ()
        }
      _ => ()
    }
    match out[out.length() - 2] {
      @lib.LocalTee(local_idx) =>
        match
          precompute_raw_try_fold_binary(
            out[out.length() - 4],
            out[out.length() - 3],
            out[out.length() - 1],
          ) {
          Some(folded) => {
            let value = out[out.length() - 3]
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            ignore(out.pop())
            out.push(value)
            out.push(@lib.Instruction::local_set(local_idx))
            out.push(folded)
            return true
          }
          None => ()
        }
      _ => ()
    }
  }
  if out.length() >= 3 {
    match
      precompute_raw_try_fold_binary(
        out[out.length() - 3],
        out[out.length() - 2],
        out[out.length() - 1],
      ) {
      Some(folded) => {
        ignore(out.pop())
        ignore(out.pop())
        ignore(out.pop())
        out.push(folded)
        return true
      }
      None => ()
    }
  }
  false
}

///|
fn precompute_raw_fold_block_local_constants(
  instrs : Array[@lib.Instruction],
) -> Bool {
  let fact_locals : Array[Int32] = []
  let fact_values : Array[@lib.Instruction?] = []
  let mut changed = false
  for idx = 0; idx < instrs.length(); idx = idx + 1 {
    match instrs[idx] {
      @lib.LocalSet(@lib.LocalIdx(raw_local))
      | @lib.LocalTee(@lib.LocalIdx(raw_local)) => {
        let local_id = raw_local.reinterpret_as_int()
        let value = if idx > 0 &&
          precompute_raw_is_literal_const(instrs[idx - 1]) {
          Some(instrs[idx - 1])
        } else {
          None
        }
        fact_locals.push(local_id)
        fact_values.push(value)
      }
      @lib.LocalGet(@lib.LocalIdx(raw_local)) => {
        let local_id = raw_local.reinterpret_as_int()
        let mut fact_idx = fact_locals.length() - 1
        while fact_idx >= 0 {
          if fact_locals[fact_idx] == local_id {
            match fact_values[fact_idx] {
              Some(value) => {
                instrs[idx] = value
                changed = true
              }
              None => ()
            }
            break
          }
          fact_idx = fact_idx - 1
        }
      }
      @lib.Block(_, @lib.Expr(body)) => {
        let mut straight_line = !precompute_raw_instrs_have_label_relative_branch(
          body,
        )
        for nested in body {
          match nested {
            @lib.Block(_, _)
            | @lib.Loop(_, _)
            | @lib.If(_, _, _)
            | @lib.Try(_, _, _, _)
            | @lib.TryTable(_, _, _)
            | @lib.Br(_)
            | @lib.BrIf(_)
            | @lib.BrTable(_, _)
            | @lib.Return
            | @lib.Unreachable => straight_line = false
            _ => ()
          }
        }
        if straight_line {
          for nested_idx = 0
              nested_idx < body.length()
              nested_idx = nested_idx + 1 {
            match body[nested_idx] {
              @lib.LocalSet(@lib.LocalIdx(raw_local))
              | @lib.LocalTee(@lib.LocalIdx(raw_local)) => {
                fact_locals.push(raw_local.reinterpret_as_int())
                fact_values.push(
                  if nested_idx > 0 &&
                    precompute_raw_is_literal_const(body[nested_idx - 1]) {
                    Some(body[nested_idx - 1])
                  } else {
                    None
                  },
                )
              }
              _ => ()
            }
          }
        } else {
          fact_locals.clear()
          fact_values.clear()
        }
      }
      @lib.Loop(_, _)
      | @lib.If(_, _, _)
      | @lib.Try(_, _, _, _)
      | @lib.TryTable(_, _, _)
      | @lib.Br(_)
      | @lib.BrIf(_)
      | @lib.BrTable(_, _)
      | @lib.Return
      | @lib.Unreachable => {
        fact_locals.clear()
        fact_values.clear()
      }
      _ => ()
    }
  }
  changed
}

///|
fn precompute_raw_void_block_is_selector_only_self_br_table(
  instrs : Array[@lib.Instruction],
) -> Bool {
  if instrs.length() != 2 {
    return false
  }
  match instrs[0] {
    @lib.I32Const(_) => ()
    _ => return false
  }
  let (targets, fallback) = match instrs[1] {
    @lib.BrTable(targets, fallback) => (targets, fallback)
    _ => return false
  }
  if fallback != @lib.LabelIdx::new(0U) {
    return false
  }
  for target in targets {
    if target != @lib.LabelIdx::new(0U) {
      return false
    }
  }
  true
}

///|
fn precompute_raw_block_type_arities(
  block_type : @lib.BlockType,
  module_ctx : @ir.HotModuleContext?,
) -> (Int32, Int32)? {
  match block_type {
    @lib.VoidBlockType => Some((0, 0))
    @lib.ValTypeBlockType(_) => Some((0, 1))
    @lib.TypeIdxBlockType(type_idx) =>
      match module_ctx {
        Some(ctx) =>
          match @ir.hot_module_resolve_type_func_type(ctx, type_idx) {
            Some(@lib.FuncType(params, results)) =>
              Some((params.length(), results.length()))
            None => None
          }
        None => None
      }
  }
}

///|
fn precompute_raw_block_label_arity(
  block_type : @lib.BlockType,
  module_ctx : @ir.HotModuleContext?,
) -> Int32? {
  match precompute_raw_block_type_arities(block_type, module_ctx) {
    Some((_, results)) => Some(results)
    None => None
  }
}

///|
fn precompute_raw_loop_label_arity(
  block_type : @lib.BlockType,
  module_ctx : @ir.HotModuleContext?,
) -> Int32? {
  match precompute_raw_block_type_arities(block_type, module_ctx) {
    Some((params, _)) => Some(params)
    None => None
  }
}

///|
fn precompute_raw_block_can_flatten_terminal(
  block_type : @lib.BlockType,
  module_ctx : @ir.HotModuleContext?,
) -> Bool {
  match precompute_raw_block_type_arities(block_type, module_ctx) {
    Some((0, _)) => true
    _ => false
  }
}

///|
fn precompute_raw_append_label_arity(
  label_arities : Array[Int32?],
  arity : Int32?,
) -> Array[Int32?] {
  let nested = label_arities.copy()
  nested.push(arity)
  nested
}

///|
fn precompute_raw_branch_target_arity(
  label_arities : Array[Int32?],
  depth : UInt32,
) -> Int32? {
  let slot = label_arities.length() - 1 - depth.reinterpret_as_int()
  if slot < 0 || slot >= label_arities.length() {
    return None
  }
  label_arities[slot]
}

///|
fn precompute_raw_rewrite_instr(
  instr : @lib.Instruction,
  module_ctx : @ir.HotModuleContext?,
  label_arities : Array[Int32?],
) -> (@lib.Instruction, Bool) {
  match instr {
    @lib.GlobalGet(global_idx) =>
      match precompute_raw_global_const_instr(module_ctx, global_idx) {
        Some(rewritten) => (rewritten, true)
        None => (instr, false)
      }
    @lib.Block(block_type, @lib.Expr(body)) => {
      let (next_body, nested_changed) = precompute_raw_rewrite_instrs(
        body,
        module_ctx,
        label_arities=precompute_raw_append_label_arity(
          label_arities,
          precompute_raw_block_label_arity(block_type, module_ctx),
        ),
      )
      let local_changed = precompute_raw_fold_block_local_constants(next_body)
      let changed = nested_changed || local_changed
      match precompute_raw_block_type_arities(block_type, module_ctx) {
        Some((0, 1)) if next_body.length() > 0 => {
          let mut literal_only = true
          for idx = 0; idx + 1 < next_body.length(); idx = idx + 1 {
            if next_body[idx] != @lib.Instruction::nop() {
              literal_only = false
            }
          }
          if literal_only &&
            precompute_raw_is_literal_const(next_body[next_body.length() - 1]) {
            return (next_body[next_body.length() - 1], true)
          }
        }
        _ => ()
      }
      match (block_type, next_body) {
        (
          @lib.ValTypeBlockType(_),
          [
            @lib.Loop(
              @lib.VoidBlockType,
              @lib.Expr([value, @lib.Br(@lib.LabelIdx(1U))])
            ),
          ],
        ) if precompute_raw_is_literal_const(value) => return (value, true)
        (
          @lib.ValTypeBlockType(_),
          [
            @lib.Loop(
              @lib.VoidBlockType,
              @lib.Expr([value, @lib.Br(@lib.LabelIdx(1U))])
            ),
            @lib.Unreachable,
          ],
        ) if precompute_raw_is_literal_const(value) => return (value, true)
        _ => ()
      }
      if block_type == @lib.BlockType::void_() {
        if precompute_raw_void_block_is_selector_only_self_br_table(next_body) {
          return (@lib.Instruction::nop(), true)
        }
        if next_body.length() == 1 {
          match next_body[0] {
            @lib.Br(@lib.LabelIdx(0U)) => return (@lib.Instruction::nop(), true)
            _ => ()
          }
        }
        if precompute_raw_instrs_are_all_nops(next_body) {
          return (@lib.Instruction::nop(), true)
        }
      }
      if changed {
        (@lib.Instruction::block(block_type, @lib.Expr::new(next_body)), true)
      } else {
        (instr, false)
      }
    }
    @lib.Loop(block_type, @lib.Expr(body)) => {
      let (next_body, changed) = precompute_raw_rewrite_instrs(
        body,
        module_ctx,
        label_arities=precompute_raw_append_label_arity(
          label_arities,
          precompute_raw_loop_label_arity(block_type, module_ctx),
        ),
      )
      if block_type == @lib.BlockType::void_() &&
        precompute_raw_instrs_are_all_nops(next_body) {
        return (@lib.Instruction::nop(), true)
      }
      if changed {
        (@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)), true)
      } else {
        (instr, false)
      }
    }
    @lib.If(block_type, then_body, else_body) => {
      let arm_label_arities = precompute_raw_append_label_arity(
        label_arities,
        precompute_raw_block_label_arity(block_type, module_ctx),
      )
      let (next_then, then_changed) = precompute_raw_rewrite_instrs(
        then_body,
        module_ctx,
        label_arities=arm_label_arities,
      )
      let mut changed = then_changed
      let next_else = match else_body {
        Some(body) => {
          let (rewritten, else_changed) = precompute_raw_rewrite_instrs(
            body,
            module_ctx,
            label_arities=arm_label_arities,
          )
          if else_changed {
            changed = true
          }
          Some(rewritten)
        }
        None => None
      }
      if changed {
        (@lib.Instruction::if_(block_type, next_then, next_else), true)
      } else {
        (instr, false)
      }
    }
    @lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
      // Share one temporary label frame across the protected and catch bodies.
      label_arities.push(
        precompute_raw_block_label_arity(block_type, module_ctx),
      )
      let (next_body, body_changed) = precompute_raw_rewrite_instrs(
        body,
        module_ctx,
        label_arities~,
      )
      let mut next_catches : Array[@lib.LegacyCatch]? = None
      for idx = 0; idx < catches.length(); idx = idx + 1 {
        let catch_ = catches[idx]
        let catch_body = match catch_ {
          @lib.LegacyCatch(_, @lib.Expr(body))
          | @lib.LegacyCatchAll(@lib.Expr(body)) => body
        }
        let (next_catch_body, changed) = precompute_raw_rewrite_instrs(
          catch_body,
          module_ctx,
          label_arities~,
        )
        if changed {
          let out = match next_catches {
            Some(out) => out
            None => {
              let out = catches.copy()
              next_catches = Some(out)
              out
            }
          }
          out[idx] = match catch_ {
            @lib.LegacyCatch(tag_idx, _) =>
              @lib.LegacyCatch::tagged(tag_idx, @lib.Expr::new(next_catch_body))
            @lib.LegacyCatchAll(_) =>
              @lib.LegacyCatch::all(@lib.Expr::new(next_catch_body))
          }
        }
      }
      ignore(label_arities.pop())
      if body_changed || next_catches is Some(_) {
        (
          @lib.Instruction::try_(
            block_type,
            @lib.Expr::new(next_body),
            next_catches.unwrap_or(catches),
            delegate~,
          ),
          true,
        )
      } else {
        (instr, false)
      }
    }
    @lib.TryTable(block_type, catches, @lib.Expr(body)) => {
      let (next_body, changed) = precompute_raw_rewrite_instrs(
        body,
        module_ctx,
        label_arities=precompute_raw_append_label_arity(
          label_arities,
          precompute_raw_block_label_arity(block_type, module_ctx),
        ),
      )
      if next_body.length() == 0 {
        (
          @lib.Instruction::try_table(
            block_type,
            catches,
            @lib.Expr::new([@lib.Instruction::nop()]),
          ),
          true,
        )
      } else if changed {
        (
          @lib.Instruction::try_table(
            block_type,
            catches,
            @lib.Expr::new(next_body),
          ),
          true,
        )
      } else {
        (instr, false)
      }
    }
    _ => (instr, false)
  }
}

///|
fn precompute_raw_instr_branches_out_of_enclosing_sequence(
  instr : @lib.Instruction,
) -> Bool {
  let body = match instr {
    @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) => body
    _ => return false
  }
  match precompute_raw_block_terminal_branch_depth(body) {
    Some(depth) => depth.reinterpret_as_int() > 0
    None => false
  }
}

///|
fn precompute_raw_instr_terminates_sequence(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.Br(_) | @lib.Return | @lib.Unreachable => true
    _ => precompute_raw_instr_branches_out_of_enclosing_sequence(instr)
  }
}

///|
fn precompute_raw_instr_is_transparent_before_unreachable(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.Nop => true
    @lib.TryTable(@lib.VoidBlockType, _, @lib.Expr(body)) =>
      precompute_raw_instrs_are_all_nops(body)
    _ => false
  }
}

///|
fn precompute_raw_instrs_are_nops_then_unreachable(
  instrs : Array[@lib.Instruction],
) -> Bool {
  if instrs.length() == 0 ||
    instrs[instrs.length() - 1] != @lib.Instruction::unreachable_() {
    return false
  }
  for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
    if instrs[idx] != @lib.Instruction::nop() {
      return false
    }
  }
  true
}

///|
fn precompute_raw_terminal_prefix_stack_effect(
  instr : @lib.Instruction,
) -> (Int32, Int32)? {
  if precompute_raw_instr_is_flat_discardable_value(instr) {
    return Some((0, 1))
  }
  if precompute_raw_instr_is_nontrapping_unary_op(instr) {
    return Some((1, 1))
  }
  if precompute_raw_instr_is_nontrapping_binary_op(instr) {
    return Some((2, 1))
  }
  match instr {
    @lib.Nop => Some((0, 0))
    @lib.Drop | @lib.LocalSet(_) | @lib.GlobalSet(_) => Some((1, 0))
    @lib.LocalTee(_) => Some((1, 1))
    @lib.Select(_) => Some((3, 1))
    @lib.I32TruncF32S
    | @lib.I32TruncF32U
    | @lib.I32TruncF64S
    | @lib.I32TruncF64U
    | @lib.I64TruncF32S
    | @lib.I64TruncF32U
    | @lib.I64TruncF64S
    | @lib.I64TruncF64U
    | @lib.RefAsNonNull
    | @lib.RefCast(_, _, _) => Some((1, 1))
    @lib.I32DivS
    | @lib.I32DivU
    | @lib.I32RemS
    | @lib.I32RemU
    | @lib.I64DivS
    | @lib.I64DivU
    | @lib.I64RemS
    | @lib.I64RemU
    | @lib.RefEq => Some((2, 1))
    @lib.Block(@lib.VoidBlockType, _) | @lib.Loop(@lib.VoidBlockType, _) =>
      Some((0, 0))
    @lib.Block(@lib.ValTypeBlockType(_), _)
    | @lib.Loop(@lib.ValTypeBlockType(_), _) => Some((0, 1))
    _ => None
  }
}

///|
fn precompute_raw_terminal_branch_prefix(
  instrs : Array[@lib.Instruction],
  payload_arity : Int32,
) -> Array[@lib.Instruction]? {
  if instrs.length() == 0 {
    return None
  }
  let branch_idx = instrs.length() - 1
  let starts : Array[Int32] = []
  let ends : Array[Int32] = []
  for i in 0.. effect
      None => return None
    }
    if starts.length() < pops {
      return None
    }
    let mut start = i
    if pops > 0 {
      start = starts[starts.length() - pops]
      for _ in 0.. UInt32? {
  if instrs.length() == 0 {
    return None
  }
  let depth = match instrs[instrs.length() - 1] {
    @lib.Br(@lib.LabelIdx(depth)) => depth
    _ => return None
  }
  for i in 0..<(instrs.length() - 1) {
    match instrs[i] {
      @lib.Br(_)
      | @lib.BrIf(_)
      | @lib.BrTable(_, _)
      | @lib.BrOnNull(_)
      | @lib.BrOnNonNull(_)
      | @lib.BrOnCast(_, _, _, _)
      | @lib.BrOnCastFail(_, _, _, _)
      | @lib.BrOnCastDescEq(_, _, _)
      | @lib.BrOnCastDescEqFail(_, _, _)
      | @lib.Resume(_, _)
      | @lib.ResumeThrow(_, _, _)
      | @lib.ResumeThrowRef(_, _)
      | @lib.Try(_, _, _, _)
      | @lib.Return
      | @lib.Block(_, _)
      | @lib.Loop(_, _)
      | @lib.If(_, _, _)
      | @lib.TryTable(_, _, _) => return None
      _ => ()
    }
  }
  Some(depth)
}

///|
fn precompute_raw_block_has_flat_return_flow(
  instrs : Array[@lib.Instruction],
) -> Bool {
  if instrs.length() == 0 ||
    instrs[instrs.length() - 1] != @lib.Instruction::return_() {
    return false
  }
  for instr in instrs {
    match instr {
      @lib.Br(_)
      | @lib.BrIf(_)
      | @lib.BrTable(_, _)
      | @lib.BrOnNull(_)
      | @lib.BrOnNonNull(_)
      | @lib.BrOnCast(_, _, _, _)
      | @lib.BrOnCastFail(_, _, _, _)
      | @lib.BrOnCastDescEq(_, _, _)
      | @lib.BrOnCastDescEqFail(_, _, _)
      | @lib.Resume(_, _)
      | @lib.ResumeThrow(_, _, _)
      | @lib.ResumeThrowRef(_, _)
      | @lib.Try(_, _, _, _)
      | @lib.Block(_, _)
      | @lib.Loop(_, _)
      | @lib.If(_, _, _)
      | @lib.TryTable(_, _, _) => return false
      _ => ()
    }
  }
  true
}

///|
fn precompute_raw_rewrite_instrs(
  instrs : Array[@lib.Instruction],
  module_ctx : @ir.HotModuleContext?,
  label_arities~ : Array[Int32?] = [],
  tail_scan_visits~ : Array[Int32]? = None,
) -> (Array[@lib.Instruction], Bool) {
  let out : Array[@lib.Instruction] = []
  let mut changed = false
  for instr in instrs {
    // An empty typed identity changes no stack value or effect. Remove it
    // directly; routing this case through HOT or creating a nop adds work.
    match instr {
      @lib.Block(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) =>
        match module_ctx {
          Some(ctx) =>
            match @ir.hot_module_resolve_type_func_type(ctx, type_idx) {
              Some(@lib.FuncType(params, results)) =>
                if params.length() == 1 &&
                  results.length() == 1 &&
                  params[0] == results[0] &&
                  precompute_raw_instrs_are_all_nops(body) {
                  changed = true
                  continue
                }
              None => ()
            }
          None => ()
        }
      _ => ()
    }
    let (rewritten, nested_changed) = precompute_raw_rewrite_instr(
      instr, module_ctx, label_arities,
    )
    if rewritten == @lib.Instruction::unreachable_() {
      let mut trailing_idx = out.length() - 1
      while trailing_idx >= 0 {
        if precompute_raw_instr_is_flat_discardable_value(out[trailing_idx]) {
          out[trailing_idx] = @lib.Instruction::nop()
          trailing_idx = trailing_idx - 1
          changed = true
        } else if precompute_raw_instr_is_transparent_before_unreachable(
            out[trailing_idx],
          ) {
          trailing_idx = trailing_idx - 1
        } else {
          break
        }
      }
    }
    let mut terminates = false
    match rewritten {
      @lib.Block(_, @lib.Expr(body)) if precompute_raw_instrs_are_nops_then_unreachable(
          body,
        ) => {
        for child in body {
          out.push(child)
        }
        terminates = true
        changed = true
      }
      @lib.Block(block_type, @lib.Expr(body)) =>
        match precompute_raw_block_terminal_branch_depth(body) {
          Some(depth) => {
            if !precompute_raw_block_can_flatten_terminal(
                block_type, module_ctx,
              ) {
              out.push(rewritten)
              continue
            }
            let block_label_arities = precompute_raw_append_label_arity(
              label_arities,
              precompute_raw_block_label_arity(block_type, module_ctx),
            )
            let payload_arity = match
              precompute_raw_branch_target_arity(block_label_arities, depth) {
              Some(arity) => arity
              None => {
                out.push(rewritten)
                continue
              }
            }
            let prefix = match
              precompute_raw_terminal_branch_prefix(body, payload_arity) {
              Some(prefix) => prefix
              None => {
                out.push(rewritten)
                continue
              }
            }
            for child in prefix {
              out.push(child)
              if child != @lib.Instruction::nop() {
                while precompute_raw_fold_output_tail(
                        out,
                        module_ctx~,
                        label_arities~,
                        loop_tail_visits=tail_scan_visits,
                      ) {
                  changed = true
                }
              }
            }
            if depth > 0U {
              out.push(@lib.Instruction::br(@lib.LabelIdx::new(depth - 1U)))
              terminates = true
            }
            changed = true
          }
          None =>
            if block_type == @lib.BlockType::void_() &&
              precompute_raw_block_has_flat_return_flow(body) {
              for child in body {
                out.push(child)
              }
              changed = true
              terminates = true
            } else {
              out.push(rewritten)
            }
        }
      _ => out.push(rewritten)
    }
    if nested_changed {
      changed = true
    }
    // A nop cannot create a new tail fold. The prior prefix was checked
    // when its last non-nop instruction was appended.
    if !(out.last() is Some(@lib.Nop)) {
      while precompute_raw_fold_output_tail(
              out,
              module_ctx~,
              label_arities~,
              loop_tail_visits=tail_scan_visits,
            ) {
        changed = true
      }
    }
    if !terminates &&
      out.length() > 0 &&
      precompute_raw_instr_terminates_sequence(out[out.length() - 1]) {
      terminates = true
      if instr != instrs[instrs.length() - 1] {
        changed = true
      }
    }
    if terminates {
      break
    }
  }
  (out, changed)
}

///|
fn precompute_raw_instr_has_hot_only_candidate(
  instr : @lib.Instruction,
  module_ctx : @ir.HotModuleContext?,
) -> Bool {
  match instr {
    @lib.GlobalGet(_) => false
    @lib.BrTable(_, _)
    | @lib.StructGet(_, _)
    | @lib.StructGetS(_, _)
    | @lib.StructGetU(_, _)
    | @lib.StructAtomicGet(_, _, _)
    | @lib.StructAtomicGetS(_, _, _)
    | @lib.StructAtomicGetU(_, _, _)
    | @lib.ArrayGet(_)
    | @lib.ArrayGetS(_)
    | @lib.ArrayGetU(_)
    | @lib.ArrayAtomicGet(_, _)
    | @lib.ArrayAtomicGetS(_, _)
    | @lib.ArrayAtomicGetU(_, _)
    | @lib.ArrayLen
    | @lib.RefEq
    | @lib.RefIsNull
    | @lib.RefTest(_, _, _)
    | @lib.StringMeasureWtf16
    | @lib.StringConcat
    | @lib.StringEq
    | @lib.StringAsWtf16
    | @lib.StringViewWtf16GetCodeunit
    | @lib.StringViewWtf16Slice
    | @lib.StringNewWtf16Array => true
    @lib.Nop => false
    @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
      precompute_raw_instrs_have_hot_only_candidate(body, module_ctx)
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_hot_only_candidate(then_body, module_ctx) {
        return true
      }
      match else_body {
        Some(body) =>
          precompute_raw_instrs_have_hot_only_candidate(body, module_ctx)
        None => false
      }
    }
    @lib.TryTable(_, _, @lib.Expr(body)) =>
      precompute_raw_instrs_have_hot_only_candidate(body, module_ctx)
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_hot_only_candidate(
  instrs : Array[@lib.Instruction],
  module_ctx : @ir.HotModuleContext?,
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_hot_only_candidate(instr, module_ctx) {
      return true
    }
  }
  false
}

///|
fn precompute_raw_instr_has_global_get(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.GlobalGet(_) => true
    @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
      precompute_raw_instrs_have_global_get(body)
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_global_get(then_body) {
        return true
      }
      match else_body {
        Some(body) => precompute_raw_instrs_have_global_get(body)
        None => false
      }
    }
    @lib.TryTable(_, _, @lib.Expr(body)) =>
      precompute_raw_instrs_have_global_get(body)
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_global_get(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_global_get(instr) {
      return true
    }
  }
  false
}

///|
fn precompute_raw_func_has_global_get(func : @lib.Func) -> Bool {
  let @lib.Func(_, @lib.Expr(instrs)) = func
  precompute_raw_instrs_have_global_get(instrs)
}

///|
fn precompute_raw_instr_has_local_traffic(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.LocalGet(_) | @lib.LocalSet(_) | @lib.LocalTee(_) => true
    @lib.Block(_, @lib.Expr(body))
    | @lib.Loop(_, @lib.Expr(body))
    | @lib.TryTable(_, _, @lib.Expr(body)) =>
      precompute_raw_instrs_have_local_traffic(body)
    @lib.Try(_, @lib.Expr(body), catches, _) => {
      if precompute_raw_instrs_have_local_traffic(body) {
        return true
      }
      for catch_ in catches {
        let @lib.Expr(catch_body) = match catch_ {
          @lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
            catch_body
        }
        if precompute_raw_instrs_have_local_traffic(catch_body) {
          return true
        }
      }
      false
    }
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_local_traffic(then_body) {
        return true
      }
      match else_body {
        Some(body) => precompute_raw_instrs_have_local_traffic(body)
        None => false
      }
    }
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_local_traffic(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_local_traffic(instr) {
      return true
    }
  }
  false
}

///|
fn precompute_raw_func_has_local_traffic(func : @lib.Func) -> Bool {
  let @lib.Func(_, @lib.Expr(instrs)) = func
  precompute_raw_instrs_have_local_traffic(instrs)
}

///|
fn precompute_raw_instr_is_preserved_dropped_effect(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.Call(_)
    | @lib.CallIndirect(_, _)
    | @lib.CallRef(_)
    | @lib.LocalTee(_)
    | @lib.TableGet(_)
    | @lib.MemoryGrow(_)
    | @lib.MemoryAtomicNotify(_)
    | @lib.MemoryAtomicWait32(_)
    | @lib.MemoryAtomicWait64(_)
    | @lib.AtomicRmw(_, _, _)
    | @lib.AtomicCmpxchg(_, _, _)
    | @lib.I32DivS
    | @lib.I32DivU
    | @lib.I32RemS
    | @lib.I32RemU
    | @lib.I64DivS
    | @lib.I64DivU
    | @lib.I64RemS
    | @lib.I64RemU
    | @lib.RefAsNonNull
    | @lib.RefCast(_, _, _)
    | @lib.RefCastDescEq(_, _, _)
    | @lib.StructGet(_, _)
    | @lib.StructGetS(_, _)
    | @lib.StructGetU(_, _)
    | @lib.StructAtomicGet(_, _, _)
    | @lib.StructAtomicGetS(_, _, _)
    | @lib.StructAtomicGetU(_, _, _)
    | @lib.ArrayGet(_)
    | @lib.ArrayGetS(_)
    | @lib.ArrayGetU(_)
    | @lib.ArrayAtomicGet(_, _)
    | @lib.ArrayAtomicGetS(_, _)
    | @lib.ArrayAtomicGetU(_, _)
    | @lib.ArrayLen
    | @lib.I31GetS
    | @lib.I31GetU
    | @lib.I32Load(_)
    | @lib.I64Load(_)
    | @lib.F32Load(_)
    | @lib.F64Load(_)
    | @lib.I32Load8S(_)
    | @lib.I32Load8U(_)
    | @lib.I32Load16S(_)
    | @lib.I32Load16U(_)
    | @lib.I64Load8S(_)
    | @lib.I64Load8U(_)
    | @lib.I64Load16S(_)
    | @lib.I64Load16U(_)
    | @lib.I64Load32S(_)
    | @lib.I64Load32U(_)
    | @lib.I32AtomicLoad(_, _)
    | @lib.I64AtomicLoad(_, _)
    | @lib.I32AtomicLoad8U(_, _)
    | @lib.I32AtomicLoad16U(_, _)
    | @lib.I64AtomicLoad8U(_, _)
    | @lib.I64AtomicLoad16U(_, _)
    | @lib.I64AtomicLoad32U(_, _)
    | @lib.V128Load(_)
    | @lib.V128Load8x8S(_)
    | @lib.V128Load8x8U(_)
    | @lib.V128Load16x4S(_)
    | @lib.V128Load16x4U(_)
    | @lib.V128Load32x2S(_)
    | @lib.V128Load32x2U(_)
    | @lib.V128Load8Splat(_)
    | @lib.V128Load16Splat(_)
    | @lib.V128Load32Splat(_)
    | @lib.V128Load64Splat(_) => true
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_drop_needing_hot(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for idx = 0; idx < instrs.length(); idx = idx + 1 {
    let instr = instrs[idx]
    match instr {
      @lib.Drop =>
        if idx == 0 ||
          !precompute_raw_instr_is_preserved_dropped_effect(instrs[idx - 1]) {
          return true
        }
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if precompute_raw_instrs_have_drop_needing_hot(body) {
          return true
        }
      @lib.If(_, then_body, else_body) => {
        if precompute_raw_instrs_have_drop_needing_hot(then_body) {
          return true
        }
        match else_body {
          Some(body) =>
            if precompute_raw_instrs_have_drop_needing_hot(body) {
              return true
            }
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        if precompute_raw_instrs_have_drop_needing_hot(body) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn precompute_raw_instrs_have_const_control_candidate(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for idx = 0; idx < instrs.length(); idx = idx + 1 {
    let instr = instrs[idx]
    match instr {
      @lib.If(_, then_body, else_body) => {
        if idx > 0 {
          match precompute_raw_i32_const(instrs[idx - 1]) {
            Some(_) => return true
            None => ()
          }
        }
        if precompute_raw_instrs_have_const_control_candidate(then_body) {
          return true
        }
        match else_body {
          Some(body) =>
            if precompute_raw_instrs_have_const_control_candidate(body) {
              return true
            }
          None => ()
        }
      }
      @lib.Select(_) =>
        if idx > 0 {
          match precompute_raw_i32_const(instrs[idx - 1]) {
            Some(_) => return true
            None => ()
          }
        }
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if precompute_raw_instrs_have_const_control_candidate(body) {
          return true
        }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        if precompute_raw_instrs_have_const_control_candidate(body) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn precompute_raw_instr_is_nop_tree(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.Nop => true
    @lib.Block(@lib.VoidBlockType, @lib.Expr(body))
    | @lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) =>
      precompute_raw_instrs_are_nop_trees(body)
    _ => false
  }
}

///|
fn precompute_raw_instrs_are_nop_trees(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    if !precompute_raw_instr_is_nop_tree(instr) {
      return false
    }
  }
  true
}

///|
fn precompute_raw_instrs_are_all_nops(instrs : Array[@lib.Instruction]) -> Bool {
  for instr in instrs {
    match instr {
      @lib.Nop => ()
      _ => return false
    }
  }
  true
}

///|
fn precompute_raw_trim_redundant_root_nops(
  instrs : Array[@lib.Instruction],
) -> Bool {
  if instrs.length() <= 1 {
    return false
  }
  match instrs[instrs.length() - 1] {
    @lib.I32Const(_)
    | @lib.I64Const(_)
    | @lib.F32Const(_)
    | @lib.F64Const(_)
    | @lib.LocalGet(_)
    | @lib.Unreachable => return false
    _ => ()
  }
  let mut changed = false
  let mut idx = instrs.length() - 1
  while idx >= 0 {
    if instrs[idx] == @lib.Instruction::nop() {
      ignore(instrs.remove(idx))
      changed = true
    }
    idx = idx - 1
  }
  changed
}

///|
fn precompute_raw_root_nop_cleanup_candidate(
  instrs : Array[@lib.Instruction],
) -> Bool {
  if instrs.length() == 0 {
    return true
  }
  if instrs.length() <= 1 {
    return false
  }
  let mut all_nops = true
  for instr in instrs {
    match instr {
      @lib.Nop => ()
      _ => all_nops = false
    }
  }
  if all_nops {
    return true
  }
  match instrs[instrs.length() - 1] {
    @lib.I32Const(_) | @lib.I64Const(_) | @lib.F32Const(_) | @lib.F64Const(_) =>
      ()
    _ => return false
  }
  for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
    match instrs[idx] {
      @lib.Nop => ()
      _ => return false
    }
  }
  true
}

///|
fn precompute_run_raw_func(
  func : @lib.Func,
  module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool, String)? {
  let @lib.Func(locals, @lib.Expr(instrs)) = func
  if precompute_raw_root_nop_cleanup_candidate(instrs) {
    return None
  }
  let (rewritten, initial_changed) = precompute_raw_rewrite_instrs(
    instrs, module_ctx,
  )
  let changed = precompute_raw_trim_redundant_root_nops(rewritten) ||
    initial_changed
  if precompute_raw_instrs_are_nop_trees(rewritten) && rewritten.length() > 1 {
    return Some(
      (
        @lib.Func::new(locals, @lib.Expr::new([@lib.Instruction::nop()])),
        true,
        "raw-all-nop-collapse",
      ),
    )
  }
  if precompute_raw_instrs_have_hot_only_candidate(rewritten, module_ctx) {
    return None
  }
  let retains_raw_control_flow = changed &&
    precompute_raw_instrs_have_label_relative_branch(instrs)
  if precompute_raw_root_nop_cleanup_candidate(rewritten) {
    return None
  }
  if (
      precompute_raw_instrs_have_drop_needing_hot(rewritten) ||
      precompute_raw_instrs_have_const_control_candidate(rewritten)
    ) &&
    !retains_raw_control_flow {
    return None
  }
  Some(
    (
      if changed {
        @lib.Func::new(locals, @lib.Expr::new(rewritten))
      } else {
        func
      },
      changed,
      if retains_raw_control_flow {
        "raw-control-flow-folds"
      } else if changed {
        "raw-scalar-folds"
      } else {
        "no-precompute-candidates"
      },
    ),
  )
}

///|
fn precompute_raw_instr_has_legacy_try(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.Try(_, _, _, _) => true
    @lib.Block(_, @lib.Expr(body))
    | @lib.Loop(_, @lib.Expr(body))
    | @lib.TryTable(_, _, @lib.Expr(body)) =>
      precompute_raw_instrs_have_legacy_try(body)
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_legacy_try(then_body) {
        return true
      }
      match else_body {
        Some(body) => precompute_raw_instrs_have_legacy_try(body)
        None => false
      }
    }
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_legacy_try(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_legacy_try(instr) {
      return true
    }
  }
  false
}

///|
fn precompute_raw_instr_has_stack_switching(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.ContNew(_)
    | @lib.ContBind(_, _)
    | @lib.Suspend(_)
    | @lib.Resume(_, _)
    | @lib.ResumeThrow(_, _, _)
    | @lib.ResumeThrowRef(_, _)
    | @lib.StackSwitch(_, _) => true
    @lib.Try(_, @lib.Expr(body), catches, _) => {
      if precompute_raw_instrs_have_stack_switching(body) {
        return true
      }
      for catch_ in catches {
        let @lib.Expr(catch_body) = match catch_ {
          @lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
            catch_body
        }
        if precompute_raw_instrs_have_stack_switching(catch_body) {
          return true
        }
      }
      false
    }
    @lib.Block(_, @lib.Expr(body))
    | @lib.Loop(_, @lib.Expr(body))
    | @lib.TryTable(_, _, @lib.Expr(body)) =>
      precompute_raw_instrs_have_stack_switching(body)
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_stack_switching(then_body) {
        return true
      }
      match else_body {
        Some(body) => precompute_raw_instrs_have_stack_switching(body)
        None => false
      }
    }
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_stack_switching(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_stack_switching(instr) {
      return true
    }
  }
  false
}

///|
fn precompute_raw_instr_has_atomic_fence(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.AtomicFence(_) => true
    @lib.Block(_, @lib.Expr(body))
    | @lib.Loop(_, @lib.Expr(body))
    | @lib.TryTable(_, _, @lib.Expr(body)) =>
      precompute_raw_instrs_have_atomic_fence(body)
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_atomic_fence(then_body) {
        return true
      }
      match else_body {
        Some(body) => precompute_raw_instrs_have_atomic_fence(body)
        None => false
      }
    }
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_atomic_fence(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_atomic_fence(instr) {
      return true
    }
  }
  false
}

///|
fn precompute_raw_instr_has_memory_grow(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.MemoryGrow(_) => true
    @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
      precompute_raw_instrs_have_memory_grow(body)
    @lib.If(_, then_body, else_body) => {
      if precompute_raw_instrs_have_memory_grow(then_body) {
        return true
      }
      match else_body {
        Some(body) => precompute_raw_instrs_have_memory_grow(body)
        None => false
      }
    }
    @lib.TryTable(_, _, @lib.Expr(body)) =>
      precompute_raw_instrs_have_memory_grow(body)
    _ => false
  }
}

///|
fn precompute_raw_instrs_have_unreachable_suffix(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for idx = 0; idx < instrs.length(); idx = idx + 1 {
    let instr = instrs[idx]
    match instr {
      @lib.Unreachable if idx + 1 < instrs.length() => return true
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if precompute_raw_instrs_have_unreachable_suffix(body) {
          return true
        }
      @lib.If(_, then_body, else_body) => {
        if precompute_raw_instrs_have_unreachable_suffix(then_body) {
          return true
        }
        match else_body {
          Some(body) =>
            if precompute_raw_instrs_have_unreachable_suffix(body) {
              return true
            }
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        if precompute_raw_instrs_have_unreachable_suffix(body) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn precompute_raw_instrs_have_memory_grow(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    if precompute_raw_instr_has_memory_grow(instr) {
      return true
    }
  }
  false
}

///|
fn precompute_run_raw_around_atomic_fence(
  func : @lib.Func,
  module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool)? {
  let @lib.Func(locals, @lib.Expr(instrs)) = func
  if !precompute_raw_instrs_have_atomic_fence(instrs) {
    return None
  }
  let (rewritten, changed) = precompute_raw_rewrite_instrs(instrs, module_ctx)
  Some((@lib.Func::new(locals, @lib.Expr::new(rewritten)), changed))
}

///|
fn precompute_raw_default_local_const(ty : @lib.ValType) -> @lib.Instruction? {
  match ty {
    @lib.NumTypeValType(@lib.I32NumType) =>
      Some(@lib.Instruction::i32_const(@lib.I32(0)))
    @lib.NumTypeValType(@lib.I64NumType) =>
      Some(@lib.Instruction::i64_const(@lib.I64(0L)))
    @lib.NumTypeValType(@lib.F32NumType) =>
      Some(@lib.Instruction::f32_const(@lib.F32(0.0)))
    @lib.NumTypeValType(@lib.F64NumType) =>
      Some(@lib.Instruction::f64_const(@lib.F64(0.0)))
    @lib.RefTypeValType(ref_type) =>
      Some(@lib.Instruction::ref_null_type(ref_type))
    _ => None
  }
}

///|
fn precompute_raw_instr_is_foldable_binary(instr : @lib.Instruction) -> Bool {
  if precompute_raw_instr_is_nontrapping_binary_op(instr) {
    return true
  }
  match instr {
    @lib.I32DivS
    | @lib.I32DivU
    | @lib.I32RemS
    | @lib.I32RemU
    | @lib.I64DivS
    | @lib.I64DivU
    | @lib.I64RemS
    | @lib.I64RemU
    | @lib.F32Add
    | @lib.F32Sub
    | @lib.F32Mul
    | @lib.F32Div
    | @lib.F32Eq
    | @lib.F32Ne
    | @lib.F32Lt
    | @lib.F32Gt
    | @lib.F32Le
    | @lib.F32Ge
    | @lib.F64Add
    | @lib.F64Sub
    | @lib.F64Mul
    | @lib.F64Div
    | @lib.F64Eq
    | @lib.F64Ne
    | @lib.F64Lt
    | @lib.F64Gt
    | @lib.F64Le
    | @lib.F64Ge => true
    _ => false
  }
}

///|
fn precompute_raw_mark_written_locals(
  instrs : Array[@lib.Instruction],
  written : Array[Bool],
) -> Unit {
  for instr in instrs {
    match instr {
      @lib.LocalSet(@lib.LocalIdx(raw_local))
      | @lib.LocalTee(@lib.LocalIdx(raw_local)) => {
        let local_id = raw_local.reinterpret_as_int()
        if local_id >= 0 && local_id < written.length() {
          written[local_id] = true
        }
      }
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        precompute_raw_mark_written_locals(body, written)
      @lib.If(_, then_body, else_body) => {
        precompute_raw_mark_written_locals(then_body, written)
        match else_body {
          Some(body) => precompute_raw_mark_written_locals(body, written)
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        precompute_raw_mark_written_locals(body, written)
      _ => ()
    }
  }
}

///|
fn precompute_propagate_same_literal(
  left : @lib.Instruction?,
  right : @lib.Instruction?,
) -> Bool {
  match (left, right) {
    (Some(@lib.F32Const(@lib.F32(a))), Some(@lib.F32Const(@lib.F32(b)))) =>
      a.reinterpret_as_uint() == b.reinterpret_as_uint()
    (Some(@lib.F64Const(@lib.F64(a))), Some(@lib.F64Const(@lib.F64(b)))) =>
      a.reinterpret_as_uint64() == b.reinterpret_as_uint64()
    _ => left == right
  }
}

///|
fn precompute_propagate_run_raw_instrs(
  instrs : Array[@lib.Instruction],
  local_values : Array[@lib.Instruction?],
  module_ctx : @ir.HotModuleContext?,
) -> (Array[@lib.Instruction], Array[@lib.Instruction?], Bool)? {
  let stack : Array[@lib.Instruction?] = []
  let rewritten : Array[@lib.Instruction] = []
  let mut changed = false
  for instr_idx = 0; instr_idx < instrs.length(); instr_idx = instr_idx + 1 {
    let instr = instrs[instr_idx]
    match instr {
      @lib.I32Const(_)
      | @lib.I64Const(_)
      | @lib.F32Const(_)
      | @lib.F64Const(_)
      | @lib.RefNull(_)
      | @lib.StringConst(_) => {
        rewritten.push(instr)
        stack.push(Some(instr))
      }
      @lib.LocalGet(@lib.LocalIdx(raw_local)) => {
        let local_id = raw_local.reinterpret_as_int()
        if local_id < 0 || local_id >= local_values.length() {
          return None
        }
        match local_values[local_id] {
          Some(value) => {
            rewritten.push(value)
            stack.push(Some(value))
            changed = true
          }
          None => {
            rewritten.push(instr)
            stack.push(None)
          }
        }
      }
      @lib.LocalSet(@lib.LocalIdx(raw_local)) => {
        let local_id = raw_local.reinterpret_as_int()
        if local_id < 0 ||
          local_id >= local_values.length() ||
          stack.length() == 0 {
          return None
        }
        local_values[local_id] = stack.pop().unwrap()
        rewritten.push(instr)
      }
      @lib.LocalTee(@lib.LocalIdx(raw_local)) => {
        let local_id = raw_local.reinterpret_as_int()
        if local_id < 0 ||
          local_id >= local_values.length() ||
          stack.length() == 0 {
          return None
        }
        local_values[local_id] = stack[stack.length() - 1]
        rewritten.push(instr)
      }
      @lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) => {
        let before_values = local_values.copy()
        let body_values_in = local_values.copy()
        let written = Array::make(local_values.length(), false)
        precompute_raw_mark_written_locals(body, written)
        for local_id = 0
            local_id < body_values_in.length()
            local_id = local_id + 1 {
          if written[local_id] {
            body_values_in[local_id] = None
          }
        }
        let (next_body, body_values, body_changed) = match
          precompute_propagate_run_raw_instrs(body, body_values_in, module_ctx) {
          Some(result) => result
          None => return None
        }
        for local_id = 0
            local_id < local_values.length()
            local_id = local_id + 1 {
          if precompute_propagate_same_literal(
              before_values[local_id],
              body_values[local_id],
            ) {
            local_values[local_id] = before_values[local_id]
          } else {
            local_values[local_id] = None
          }
        }
        rewritten.push(
          @lib.Instruction::loop_(
            @lib.BlockType::void_(),
            @lib.Expr::new(next_body),
          ),
        )
        if body_changed {
          changed = true
        }
      }
      @lib.If(block_type, then_body, else_body) => {
        if stack.length() == 0 {
          return None
        }
        ignore(stack.pop())
        let has_result = match block_type {
          @lib.VoidBlockType => false
          @lib.ValTypeBlockType(_) => true
          @lib.TypeIdxBlockType(_) => return None
        }
        if has_result && else_body is None {
          return None
        }
        let (next_then, then_values, then_changed) = match
          precompute_propagate_run_raw_instrs(
            then_body,
            local_values.copy(),
            module_ctx,
          ) {
          Some(result) => result
          None => return None
        }
        let (next_else, else_values, else_changed) = match else_body {
          Some(body) =>
            match
              precompute_propagate_run_raw_instrs(
                body,
                local_values.copy(),
                module_ctx,
              ) {
              Some((next_body, values, body_changed)) =>
                (Some(next_body), values, body_changed)
              None => return None
            }
          None => (None, local_values.copy(), false)
        }
        for local_id = 0
            local_id < local_values.length()
            local_id = local_id + 1 {
          if precompute_propagate_same_literal(
              then_values[local_id],
              else_values[local_id],
            ) {
            local_values[local_id] = then_values[local_id]
          } else {
            local_values[local_id] = None
          }
        }
        rewritten.push(@lib.Instruction::if_(block_type, next_then, next_else))
        if has_result {
          stack.push(None)
        }
        if then_changed || else_changed {
          changed = true
        }
      }
      @lib.I32Store(_)
      | @lib.I64Store(_)
      | @lib.F32Store(_)
      | @lib.F64Store(_)
      | @lib.I32Store8(_)
      | @lib.I32Store16(_)
      | @lib.I64Store8(_)
      | @lib.I64Store16(_)
      | @lib.I64Store32(_) => {
        if stack.length() < 2 {
          return None
        }
        ignore(stack.pop())
        ignore(stack.pop())
        rewritten.push(instr)
      }
      @lib.MemorySize(_) => {
        rewritten.push(instr)
        stack.push(None)
      }
      @lib.MemoryGrow(_) => {
        if stack.length() == 0 {
          return None
        }
        ignore(stack.pop())
        rewritten.push(instr)
        stack.push(None)
      }
      @lib.Drop => {
        if stack.length() == 0 {
          return None
        }
        ignore(stack.pop())
        rewritten.push(instr)
      }
      @lib.GlobalSet(_) => {
        if stack.length() == 0 {
          return None
        }
        ignore(stack.pop())
        rewritten.push(instr)
      }
      @lib.Call(_) => {
        rewritten.push(instr)
        stack.clear()
      }
      @lib.Br(_) => {
        rewritten.push(instr)
        for rest_idx = instr_idx + 1
            rest_idx < instrs.length()
            rest_idx = rest_idx + 1 {
          rewritten.push(instrs[rest_idx])
        }
        return Some((rewritten, local_values, changed))
      }
      @lib.Nop => rewritten.push(instr)
      @lib.Unreachable => {
        rewritten.push(instr)
        stack.clear()
      }
      op if precompute_raw_instr_is_nontrapping_unary_op(op) => {
        if stack.length() == 0 {
          return None
        }
        let operand = stack.pop().unwrap()
        stack.push(
          match operand {
            Some(value) => precompute_raw_try_fold_unary(value, op)
            None => None
          },
        )
        rewritten.push(instr)
      }
      op if precompute_raw_instr_is_foldable_binary(op) => {
        if stack.length() < 2 {
          return None
        }
        let rhs = stack.pop().unwrap()
        let lhs = stack.pop().unwrap()
        stack.push(
          match (lhs, rhs) {
            (Some(lhs), Some(rhs)) =>
              precompute_raw_try_fold_binary(lhs, rhs, op)
            _ => None
          },
        )
        rewritten.push(instr)
      }
      _ => {
        if !changed {
          return None
        }
        rewritten.push(instr)
        for rest_idx = instr_idx + 1
            rest_idx < instrs.length()
            rest_idx = rest_idx + 1 {
          rewritten.push(instrs[rest_idx])
        }
        for local_id = 0
            local_id < local_values.length()
            local_id = local_id + 1 {
          local_values[local_id] = None
        }
        let (folded, folded_changed) = precompute_raw_rewrite_instrs(
          rewritten, module_ctx,
        )
        return Some((folded, local_values, changed || folded_changed))
      }
    }
  }
  let (folded, folded_changed) = precompute_raw_rewrite_instrs(
    rewritten, module_ctx,
  )
  Some((folded, local_values, changed || folded_changed))
}

///|
fn precompute_propagate_run_raw_unreachable_suffix(
  func : @lib.Func,
  params : Array[@lib.ValType],
  module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool)? {
  let @lib.Func(_, @lib.Expr(original_instrs)) = func
  if !precompute_raw_instrs_have_unreachable_suffix(original_instrs) {
    return None
  }
  let (propagated, propagated_changed) = match
    precompute_propagate_run_raw_straight_line_func(func, params, module_ctx) {
    Some(result) => result
    None => (func, false)
  }
  let @lib.Func(locals, @lib.Expr(propagated_instrs)) = propagated
  let (rewritten, cleanup_changed) = precompute_raw_rewrite_instrs(
    propagated_instrs, module_ctx,
  )
  Some(
    (
      @lib.Func::new(locals, @lib.Expr::new(rewritten)),
      propagated_changed || cleanup_changed,
    ),
  )
}

///|
fn precompute_propagate_run_raw_straight_line_func(
  func : @lib.Func,
  params : Array[@lib.ValType],
  module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool)? {
  let @lib.Func(locals, @lib.Expr(instrs)) = func
  let local_values : Array[@lib.Instruction?] = Array::make(
    params.length() + locals.length(),
    None,
  )
  for idx = 0; idx < locals.length(); idx = idx + 1 {
    local_values[params.length() + idx] = precompute_raw_default_local_const(
      locals.get(idx),
    )
  }
  match precompute_propagate_run_raw_instrs(instrs, local_values, module_ctx) {
    Some((rewritten, _, changed)) =>
      Some((@lib.Func::new(locals, @lib.Expr::new(rewritten)), changed))
    None => None
  }
}

///|
fn precompute_has_transparent_value_block_child(
  func : @ir.HotFunc,
  node_id : Int32,
  label_used : @bitset.BitSet,
) -> Bool {
  if @ir.hot_is_control_node(func, node_id) {
    return false
  }
  for slot in 0..<@ir.hot_child_count(func, node_id) {
    let child = @ir.hot_child_get(func, node_id, slot)
    if child >= 0 &&
      @ir.hot_node_is_live(func, child) &&
      @ir.hot_node_get(func, child).op is @ir.HotOp::Block &&
      @ir.hot_node_result_arity(func, child) == 1 &&
      !pass_label_is_used(@ir.hot_control_node_label(func, child), label_used) &&
      @ir.hot_region_root_count(func, @ir.hot_region_body_ref(func, child)) == 1 {
      return true
    }
  }
  false
}

///|
fn precompute_run(ctx : HotPassContext, func : @ir.HotFunc) -> HotPassResult {
  let mut changed_any = false
  let mut changed_round = true
  while changed_round {
    changed_round = false
    let entry_label_used = pass_compute_label_used(func)
    let simplified = precompute_simplify_region_roots(
      ctx,
      func,
      @ir.HotRegionRef::root(),
      entry_label_used,
    )
    if simplified {
      changed_round = true
      changed_any = true
    }
    let label_used = if simplified {
      pass_compute_label_used(func)
    } else {
      entry_label_used
    }
    let mut inline_candidates : Array[Int32]? = None
    for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
      if !@ir.hot_node_is_live(func, node_id) {
        continue
      }
      if precompute_try_refinalize_exact_cast_block(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      if precompute_try_simplify_self_branch_value_block(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      if precompute_has_transparent_value_block_child(func, node_id, label_used) {
        match inline_candidates {
          Some(candidates) => candidates.push(node_id)
          None => inline_candidates = Some([node_id])
        }
      }
      if precompute_try_fold_global_get(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      if precompute_try_eliminate_dead_drop(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      if precompute_try_fold_void_br_if(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      if precompute_try_collapse_first_unreachable_child(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      if precompute_try_fold_constant_if(ctx, func, node_id) ||
        precompute_try_remove_exact_zero_memory_init(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      if precompute_try_partial_select_condition(ctx, func, node_id) ||
        precompute_try_partial_heap_select_parent(ctx, func, node_id) ||
        precompute_try_fold_constant_select(ctx, func, node_id) ||
        precompute_try_fold_string_slice(ctx, func, node_id) ||
        precompute_try_fold_fresh_string_new_wtf16(ctx, func, node_id) ||
        precompute_try_fold_descriptor_branch(ctx, func, node_id) ||
        precompute_try_fold_null_ref_cast(ctx, func, node_id) ||
        precompute_try_fold_null_aggregate_trap(ctx, func, node_id) ||
        precompute_try_fold_ref_is_null(ctx, func, node_id) ||
        precompute_try_partial_ref_eq_select(ctx, func, node_id) ||
        precompute_try_fold_gc_identity(ctx, func, node_id) ||
        precompute_try_fold_fresh_ref_test(ctx, func, node_id) ||
        precompute_try_fold_fresh_struct_get(ctx, func, node_id) ||
        precompute_try_fold_fresh_array_len(ctx, func, node_id) ||
        precompute_try_fold_fresh_array_get(ctx, func, node_id) {
        changed_round = true
        changed_any = true
        continue
      }
      let instr = match @ir.hot_node_exact_instr(func, node_id) {
        Some(instr) => instr
        None => continue
      }
      if precompute_try_partial_select_parent(ctx, func, node_id, instr) ||
        precompute_try_fold_effectful_parent(ctx, func, node_id, instr) {
        changed_round = true
        changed_any = true
        continue
      }
      let child_count = @ir.hot_child_count(func, node_id)
      let changed = if child_count == 1 {
        precompute_try_fold_unary(
          ctx,
          func,
          node_id,
          instr,
          @ir.hot_child_get(func, node_id, 0),
        )
      } else if child_count == 2 {
        precompute_try_fold_binary(
          ctx,
          func,
          node_id,
          instr,
          @ir.hot_child_get(func, node_id, 0),
          @ir.hot_child_get(func, node_id, 1),
        )
      } else {
        false
      }
      if changed {
        changed_round = true
        changed_any = true
      }
    }
    // Only this phase changes ownership while its count snapshot is live.
    // An inline transfers the sole holder edge to the value; terminating
    // replacement detaches the copied value's old edges. Counts can therefore
    // only stay equal or decrease. Shared holders remain for later rounds.
    // Check and delete all detached nodes once after the phase, rather than
    // scanning the complete function for each empty holder.
    match inline_candidates {
      Some(candidates) => {
        let label_used = pass_compute_label_used(func)
        let mut counts : @ir.HotNodeUseCounts? = None
        let cleanup = PrecomputeTransparentBlockCleanup::new()
        for node_id in candidates {
          if !@ir.hot_node_is_live(func, node_id) ||
            !precompute_has_transparent_value_block_child(
              func, node_id, label_used,
            ) {
            continue
          }
          let use_counts = match counts {
            Some(counts) => counts
            None => {
              let snapshot = @ir.hot_node_use_counts_build(func)
              counts = Some(snapshot)
              snapshot
            }
          }
          if @ir.hot_node_use_count(use_counts, node_id) > 0 &&
            precompute_try_inline_transparent_value_block_child(
              ctx,
              func,
              node_id,
              label_used,
              use_counts,
              cleanup=Some(cleanup),
            ) {
            changed_round = true
            changed_any = true
          }
        }
        precompute_finish_transparent_block_cleanup(func, cleanup)
      }
      None => ()
    }
    if changed_round &&
      precompute_trim_region_nops(ctx, func, @ir.HotRegionRef::root()) {
      changed_round = true
      changed_any = true
    }
    if precompute_coalesce_all_root_nops(ctx, func) {
      changed_round = true
      changed_any = true
    }
    if precompute_trim_root_nops_before_trailing_const(ctx, func) {
      changed_round = true
      changed_any = true
    }
  }
  if changed_any {
    HotPassResult::changed()
  } else {
    HotPassResult::unchanged()
  }
}

///|
fn precompute_propagate_prefix_const_payload_from_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
) -> @ir.HotConstPayload? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Const | @ir.HotOp::RefNull =>
      Some(@ir.hot_const_get(func, node.imm0))
    @ir.HotOp::GlobalGet => precompute_global_const(ctx, func, node_id)
    _ => None
  }
}

///|
fn precompute_propagate_prefix_const_payload_matches_type(
  payload : @ir.HotConstPayload,
  ty : @lib.ValType,
) -> Bool {
  match (payload, ty) {
    (@ir.HotConstPayload::I32Const(_), @lib.NumTypeValType(@lib.I32NumType)) =>
      true
    (@ir.HotConstPayload::I64Const(_), @lib.NumTypeValType(@lib.I64NumType)) =>
      true
    (@ir.HotConstPayload::F32Const(_), @lib.NumTypeValType(@lib.F32NumType)) =>
      true
    (@ir.HotConstPayload::F64Const(_), @lib.NumTypeValType(@lib.F64NumType)) =>
      true
    (@ir.HotConstPayload::RefNullConst(_), @lib.RefTypeValType(expected)) =>
      expected.is_nullable()
    (@ir.HotConstPayload::StringConst(_), @lib.RefTypeValType(expected)) =>
      match expected.get_heap_type() {
        @lib.AbsHeapTypeHeapType(@lib.StringAbsHeapType)
        | @lib.AbsHeapTypeHeapType(@lib.ExternAbsHeapType) => true
        _ => false
      }
    _ => false
  }
}

///|
fn precompute_propagate_prefix_const_instr(
  payload : @ir.HotConstPayload,
) -> @lib.Instruction? {
  match payload {
    @ir.HotConstPayload::I32Const(value) =>
      Some(@lib.Instruction::i32_const(value))
    @ir.HotConstPayload::I64Const(value) =>
      Some(@lib.Instruction::i64_const(value))
    @ir.HotConstPayload::F32Const(value) =>
      Some(@lib.Instruction::f32_const(value))
    @ir.HotConstPayload::F64Const(value) =>
      Some(@lib.Instruction::f64_const(value))
    @ir.HotConstPayload::RefNullConst(ref_type) =>
      Some(@lib.Instruction::ref_null(ref_type.get_heap_type()))
    @ir.HotConstPayload::StringConst(value) =>
      Some(@lib.Instruction::string_const(value))
  }
}

///|
fn precompute_propagate_prefix_const_payload_from_instr(
  instr : @lib.Instruction,
) -> @ir.HotConstPayload? {
  match instr {
    @lib.I32Const(value) => Some(@ir.HotConstPayload::i32_const(value))
    @lib.I64Const(value) => Some(@ir.HotConstPayload::i64_const(value))
    @lib.F32Const(value) => Some(@ir.HotConstPayload::f32_const(value))
    @lib.F64Const(value) => Some(@ir.HotConstPayload::f64_const(value))
    @lib.RefNull(ref_type) =>
      Some(@ir.HotConstPayload::ref_null_const(ref_type))
    @lib.StringConst(value) => Some(@ir.HotConstPayload::string_const(value))
    _ => None
  }
}

///|
fn precompute_propagate_prefix_default_local_const(
  func : @ir.HotFunc,
  local_id : Int32,
) -> @ir.HotConstPayload? {
  match @ir.hot_local_type(func, local_id) {
    Some(@lib.NumTypeValType(@lib.I32NumType)) =>
      Some(@ir.HotConstPayload::i32_const(@lib.I32(0)))
    Some(@lib.NumTypeValType(@lib.I64NumType)) =>
      Some(@ir.HotConstPayload::i64_const(@lib.I64(0L)))
    Some(@lib.NumTypeValType(@lib.F32NumType)) =>
      Some(@ir.HotConstPayload::f32_const(@lib.F32(0.0)))
    Some(@lib.NumTypeValType(@lib.F64NumType)) =>
      Some(@ir.HotConstPayload::f64_const(@lib.F64(0.0)))
    Some(@lib.RefTypeValType(ref_type)) =>
      Some(@ir.HotConstPayload::ref_null_const(ref_type))
    _ => None
  }
}

///|
// Resolve every transitive local read from actual reaching writes. CFG SSA
// currently omits writes in nested condition operands; following its apparent
// entry defaults through a copy can turn a dynamic result into zero.
fn precompute_propagate_prefix_const_for_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  graph : @ir.HotLocalGraph,
  label_used : @bitset.BitSet,
  node_id : Int32,
  states : Array[Int32],
  value_consts : Array[@ir.HotConstPayload?],
) -> @ir.HotConstPayload? {
  // Earlier allocation/identity folds can append constant nodes after the
  // LocalGraph snapshot. Their literal payload needs no reaching-write fact.
  match
    precompute_propagate_prefix_const_payload_from_node(ctx, func, node_id) {
    Some(payload) => return Some(payload)
    None => ()
  }
  if node_id < 0 || node_id >= states.length() {
    return None
  }
  match states[node_id] {
    1 | 3 => return None
    2 => return value_consts[node_id]
    _ => ()
  }
  states[node_id] = 1
  let value = precompute_propagate_prefix_const_for_node_impl(
    ctx, func, graph, label_used, node_id, states, value_consts,
  )
  value_consts[node_id] = value
  states[node_id] = if value.is_some() { 2 } else { 3 }
  value
}

///|
fn precompute_propagate_prefix_const_for_node_impl(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  graph : @ir.HotLocalGraph,
  label_used : @bitset.BitSet,
  node_id : Int32,
  states : Array[Int32],
  value_consts : Array[@ir.HotConstPayload?],
) -> @ir.HotConstPayload? {
  match
    precompute_propagate_prefix_const_payload_from_node(ctx, func, node_id) {
    Some(payload) => return Some(payload)
    None => ()
  }
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet => {
      let sources = @ir.local_graph_reaching_sources_for_get(graph, node_id)
      let mut candidate : @ir.HotConstPayload? = None
      for source in sources {
        let value = match source {
          @ir.HotLocalGraphSource::EntrySource(local_id) =>
            if @ir.local_graph_entry_source_has_legal_default(graph, local_id) {
              precompute_propagate_prefix_default_local_const(func, local_id)
            } else {
              None
            }
          @ir.HotLocalGraphSource::SetSource(write_id) =>
            if @ir.hot_child_count(func, write_id) == 1 {
              precompute_propagate_prefix_const_for_node(
                ctx,
                func,
                graph,
                label_used,
                @ir.hot_child_get(func, write_id, 0),
                states,
                value_consts,
              )
            } else {
              None
            }
        }
        match value {
          Some(payload) => {
            let expected_type = @ir.hot_local_type(func, node.imm0).unwrap()
            if !precompute_propagate_prefix_const_payload_matches_type(
                payload, expected_type,
              ) {
              return None
            }
            match candidate {
              Some(previous) if !precompute_propagate_same_literal(
                  precompute_propagate_prefix_const_instr(previous),
                  precompute_propagate_prefix_const_instr(payload),
                ) => return None
              None => candidate = Some(payload)
              _ => ()
            }
          }
          None => return None
        }
      }
      return candidate
    }
    // Binaryen's propagate mode reasons from a set value's fallthrough value.
    // Peel value-preserving tees and unbranched single-result blocks while
    // leaving the original wrapper and its effects in place.
    @ir.HotOp::LocalTee =>
      if @ir.hot_child_count(func, node_id) == 1 {
        return precompute_propagate_prefix_const_for_node(
          ctx,
          func,
          graph,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          value_consts,
        )
      }
    @ir.HotOp::Block => {
      let label_id = @ir.hot_control_node_label(func, node_id)
      let body_ref = @ir.hot_region_body_ref(func, node_id)
      let body_count = @ir.hot_region_root_count(func, body_ref)
      if @ir.hot_node_result_arity(func, node_id) == 1 &&
        !pass_label_is_used(label_id, label_used) &&
        body_count > 0 {
        return precompute_propagate_prefix_const_for_node(
          ctx,
          func,
          graph,
          label_used,
          @ir.hot_region_get(func, body_ref, body_count - 1),
          states,
          value_consts,
        )
      }
    }
    @ir.HotOp::If => {
      let cond_id = @ir.hot_child_get(func, node_id, 0)
      let take_then = match
        precompute_propagate_prefix_const_for_node(
          ctx, func, graph, label_used, cond_id, states, value_consts,
        ) {
        Some(@ir.HotConstPayload::I32Const(@lib.I32(0))) => false
        Some(@ir.HotConstPayload::I32Const(_)) => true
        _ => return None
      }
      let chosen = if take_then {
        Some(@ir.hot_region_then_ref(func, node_id))
      } else {
        @ir.hot_region_else_ref(func, node_id)
      }
      match chosen {
        Some(region_ref) => {
          let root_count = @ir.hot_region_root_count(func, region_ref)
          if root_count > 0 {
            return precompute_propagate_prefix_const_for_node(
              ctx,
              func,
              graph,
              label_used,
              @ir.hot_region_get(func, region_ref, root_count - 1),
              states,
              value_consts,
            )
          }
        }
        None => ()
      }
    }
    _ => ()
  }
  let instr = match @ir.hot_node_exact_instr(func, node_id) {
    Some(instr) => instr
    None => return None
  }
  let child_count = @ir.hot_child_count(func, node_id)
  if child_count == 1 {
    let operand = match
      precompute_propagate_prefix_const_for_node(
        ctx,
        func,
        graph,
        label_used,
        @ir.hot_child_get(func, node_id, 0),
        states,
        value_consts,
      ) {
      Some(payload) => payload
      None => return None
    }
    let operand_instr = match precompute_propagate_prefix_const_instr(operand) {
      Some(instr) => instr
      None => return None
    }
    return match precompute_raw_try_fold_unary(operand_instr, instr) {
      Some(next) => precompute_propagate_prefix_const_payload_from_instr(next)
      None => None
    }
  }
  if child_count == 2 {
    let lhs = match
      precompute_propagate_prefix_const_for_node(
        ctx,
        func,
        graph,
        label_used,
        @ir.hot_child_get(func, node_id, 0),
        states,
        value_consts,
      ) {
      Some(payload) => payload
      None => return None
    }
    let rhs = match
      precompute_propagate_prefix_const_for_node(
        ctx,
        func,
        graph,
        label_used,
        @ir.hot_child_get(func, node_id, 1),
        states,
        value_consts,
      ) {
      Some(payload) => payload
      None => return None
    }
    let lhs_instr = match precompute_propagate_prefix_const_instr(lhs) {
      Some(instr) => instr
      None => return None
    }
    let rhs_instr = match precompute_propagate_prefix_const_instr(rhs) {
      Some(instr) => instr
      None => return None
    }
    return match precompute_raw_try_fold_binary(lhs_instr, rhs_instr, instr) {
      Some(next) => precompute_propagate_prefix_const_payload_from_instr(next)
      None => None
    }
  }
  None
}

///|
fn precompute_propagate_mark_result_if_arm_writes(
  func : @ir.HotFunc,
  node_id : Int32,
  inside_result_if_arm : Bool,
  unsafe_locals : Array[Bool],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
      let value_id = if @ir.hot_child_count(func, node_id) == 1 {
        @ir.hot_child_get(func, node_id, 0)
      } else {
        -1
      }
      let result_if_write = value_id >= 0 &&
        @ir.hot_node_is_live(func, value_id) &&
        @ir.hot_node_result_arity(func, value_id) > 0 &&
        (match @ir.hot_node_get(func, value_id).op {
          @ir.HotOp::If => true
          _ => false
        })
      if (inside_result_if_arm || result_if_write) &&
        node.imm0 >= 0 &&
        node.imm0 < unsafe_locals.length() {
        unsafe_locals[node.imm0] = true
      }
    }
    _ => ()
  }
  match node.op {
    @ir.HotOp::If => {
      precompute_propagate_mark_result_if_arm_writes(
        func,
        @ir.hot_child_get(func, node_id, 0),
        inside_result_if_arm,
        unsafe_locals,
      )
      let arm_is_unsafe = inside_result_if_arm ||
        @ir.hot_node_result_arity(func, node_id) > 0
      let then_ref = @ir.hot_region_then_ref(func, node_id)
      for idx = 0
          idx < @ir.hot_region_root_count(func, then_ref)
          idx = idx + 1 {
        precompute_propagate_mark_result_if_arm_writes(
          func,
          @ir.hot_region_get(func, then_ref, idx),
          arm_is_unsafe,
          unsafe_locals,
        )
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          for idx = 0
              idx < @ir.hot_region_root_count(func, else_ref)
              idx = idx + 1 {
            precompute_propagate_mark_result_if_arm_writes(
              func,
              @ir.hot_region_get(func, else_ref, idx),
              arm_is_unsafe,
              unsafe_locals,
            )
          }
        None => ()
      }
    }
    _ =>
      for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
        precompute_propagate_mark_result_if_arm_writes(
          func,
          @ir.hot_child_get(func, node_id, slot),
          inside_result_if_arm,
          unsafe_locals,
        )
      }
  }
}

///|
fn precompute_propagate_collect_condition_const_writes(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  local_consts : Array[@ir.HotConstPayload?],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let node = @ir.hot_node_get(func, node_id)
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    precompute_propagate_collect_condition_const_writes(
      ctx,
      func,
      @ir.hot_child_get(func, node_id, slot),
      local_consts,
    )
  }
  match node.op {
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
      let local_id = node.imm0
      if local_id >= 0 &&
        local_id < local_consts.length() &&
        @ir.hot_child_count(func, node_id) == 1 {
        local_consts[local_id] = precompute_propagate_prefix_const_payload_from_node(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 0),
        )
      }
    }
    _ => ()
  }
}

///|
fn precompute_propagate_mark_arm_direct_const_gets(
  func : @ir.HotFunc,
  node_id : Int32,
  condition_consts : Array[@ir.HotConstPayload?],
  arm_written : Array[Bool],
  get_consts : Array[@ir.HotConstPayload?],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let node = @ir.hot_node_get(func, node_id)
  if node.op is @ir.HotOp::Loop {
    precompute_propagate_mark_result_if_arm_writes(
      func, node_id, true, arm_written,
    )
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop => {
      let body = @ir.hot_region_body_ref(func, node_id)
      for idx = 0; idx < @ir.hot_region_root_count(func, body); idx = idx + 1 {
        precompute_propagate_mark_arm_direct_const_gets(
          func,
          @ir.hot_region_get(func, body, idx),
          condition_consts,
          arm_written,
          get_consts,
        )
      }
      return
    }
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    precompute_propagate_mark_arm_direct_const_gets(
      func,
      @ir.hot_child_get(func, node_id, slot),
      condition_consts,
      arm_written,
      get_consts,
    )
  }
  match node.op {
    @ir.HotOp::LocalGet => {
      let local_id = node.imm0
      if local_id >= 0 &&
        local_id < condition_consts.length() &&
        !arm_written[local_id] {
        get_consts[node_id] = condition_consts[local_id]
      }
    }
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
      let local_id = node.imm0
      if local_id >= 0 && local_id < arm_written.length() {
        arm_written[local_id] = true
      }
    }
    _ => ()
  }
}

///|
fn precompute_propagate_mark_result_if_direct_const_gets(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  get_consts : Array[@ir.HotConstPayload?],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::If => {
      let condition_id = @ir.hot_child_get(func, node_id, 0)
      precompute_propagate_mark_result_if_direct_const_gets(
        ctx, func, condition_id, get_consts,
      )
      let then_ref = @ir.hot_region_then_ref(func, node_id)
      let else_ref = @ir.hot_region_else_ref(func, node_id)
      if @ir.hot_node_result_arity(func, node_id) > 0 {
        let condition_consts : Array[@ir.HotConstPayload?] = Array::make(
          @ir.hot_local_count(func),
          None,
        )
        precompute_propagate_collect_condition_const_writes(
          ctx, func, condition_id, condition_consts,
        )
        let then_written = Array::make(@ir.hot_local_count(func), false)
        for idx = 0
            idx < @ir.hot_region_root_count(func, then_ref)
            idx = idx + 1 {
          precompute_propagate_mark_arm_direct_const_gets(
            func,
            @ir.hot_region_get(func, then_ref, idx),
            condition_consts,
            then_written,
            get_consts,
          )
        }
        match else_ref {
          Some(region_ref) => {
            let else_written = Array::make(@ir.hot_local_count(func), false)
            for idx = 0
                idx < @ir.hot_region_root_count(func, region_ref)
                idx = idx + 1 {
              precompute_propagate_mark_arm_direct_const_gets(
                func,
                @ir.hot_region_get(func, region_ref, idx),
                condition_consts,
                else_written,
                get_consts,
              )
            }
          }
          None => ()
        }
      }
      for idx = 0
          idx < @ir.hot_region_root_count(func, then_ref)
          idx = idx + 1 {
        precompute_propagate_mark_result_if_direct_const_gets(
          ctx,
          func,
          @ir.hot_region_get(func, then_ref, idx),
          get_consts,
        )
      }
      match else_ref {
        Some(region_ref) =>
          for idx = 0
              idx < @ir.hot_region_root_count(func, region_ref)
              idx = idx + 1 {
            precompute_propagate_mark_result_if_direct_const_gets(
              ctx,
              func,
              @ir.hot_region_get(func, region_ref, idx),
              get_consts,
            )
          }
        None => ()
      }
    }
    @ir.HotOp::Block | @ir.HotOp::Loop => {
      let body = @ir.hot_region_body_ref(func, node_id)
      for idx = 0; idx < @ir.hot_region_root_count(func, body); idx = idx + 1 {
        precompute_propagate_mark_result_if_direct_const_gets(
          ctx,
          func,
          @ir.hot_region_get(func, body, idx),
          get_consts,
        )
      }
    }
    _ =>
      for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
        precompute_propagate_mark_result_if_direct_const_gets(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, slot),
          get_consts,
        )
      }
  }
}

///|
fn precompute_known_heap_struct_get(
  ctx : HotPassContext,
  value : PrecomputeKnownHeapValue,
  type_idx : @lib.TypeIdx,
  raw_field : UInt32,
  signed : Bool,
  unsigned : Bool,
) -> PrecomputeKnownHeapValue? {
  let module_ctx = match ctx.module_ctx {
    Some(module_ctx) => module_ctx
    None => return None
  }
  let fields = match
    @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
    Some(fields) => fields
    None => return None
  }
  let field_idx = raw_field.reinterpret_as_int()
  let field = match fields.get(field_idx) {
    Some(field) if !field.is_mutable() => field
    _ => return None
  }
  let raw_value = match value {
    KnownStructFields(actual_type, values) if actual_type == type_idx =>
      match values.get(field_idx) {
        Some(value) => value
        None => return None
      }
    KnownStructDefault(actual_type) if actual_type == type_idx =>
      match precompute_default_const_for_storage(field.get_storage_type()) {
        Some(value) => KnownEmit(value)
        None => return None
      }
    _ => return None
  }
  match raw_value {
    KnownEmit(instr) =>
      match
        precompute_unpack_array_value(
          instr,
          field.get_storage_type(),
          signed,
          unsigned,
        ) {
        Some(instr) => Some(KnownEmit(instr))
        None => None
      }
    _ =>
      match field.get_storage_type() {
        @lib.ValTypeStorageType(@lib.RefTypeValType(_)) if !signed && !unsigned =>
          Some(raw_value)
        _ => None
      }
  }
}

///|
fn precompute_known_heap_array_get(
  ctx : HotPassContext,
  value : PrecomputeKnownHeapValue,
  type_idx : @lib.TypeIdx,
  index : UInt32,
  signed : Bool,
  unsigned : Bool,
) -> PrecomputeKnownHeapValue? {
  let module_ctx = match ctx.module_ctx {
    Some(module_ctx) => module_ctx
    None => return None
  }
  let field = match @ir.hot_module_resolve_array_field(module_ctx, type_idx) {
    Some(field) if !field.is_mutable() => field
    _ => return None
  }
  let raw_value = match value {
    KnownArrayFixed(actual_type, values) if actual_type == type_idx =>
      match values.get(index.reinterpret_as_int()) {
        Some(value) => value
        None => return None
      }
    KnownArrayFill(actual_type, values, length) if actual_type == type_idx &&
      index < length &&
      values.length() == 1 => values[0]
    KnownArrayDefault(actual_type, length) if actual_type == type_idx &&
      index < length =>
      match precompute_default_const_for_storage(field.get_storage_type()) {
        Some(value) => KnownEmit(value)
        None => return None
      }
    _ => return None
  }
  match raw_value {
    KnownEmit(instr) =>
      match
        precompute_unpack_array_value(
          instr,
          field.get_storage_type(),
          signed,
          unsigned,
        ) {
        Some(instr) => Some(KnownEmit(instr))
        None => None
      }
    _ =>
      match field.get_storage_type() {
        @lib.ValTypeStorageType(@lib.RefTypeValType(_)) if !signed && !unsigned =>
          Some(raw_value)
        _ => None
      }
  }
}

///|
fn precompute_known_heap_array_len(
  value : PrecomputeKnownHeapValue,
) -> PrecomputeKnownHeapValue? {
  let length = match value {
    KnownArrayFixed(_, values) => values.length().reinterpret_as_uint()
    KnownArrayFill(_, _, length) | KnownArrayDefault(_, length) => length
    _ => return None
  }
  Some(
    KnownEmit(
      @lib.Instruction::i32_const(@lib.I32(length.reinterpret_as_int())),
    ),
  )
}

///|
fn precompute_known_heap_value_matches_cast(
  value : PrecomputeKnownHeapValue,
  nullable : Bool,
  target : @lib.HeapType,
) -> Bool {
  match value {
    KnownEmit(@lib.RefNull(_)) => nullable
    KnownStructFields(type_idx, _)
    | KnownStructDefault(type_idx)
    | KnownArrayFixed(type_idx, _)
    | KnownArrayFill(type_idx, _, _)
    | KnownArrayDefault(type_idx, _) => target == @lib.HeapType::new(type_idx)
    _ => false
  }
}

///|
fn precompute_propagate_known_heap_value_for_node(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  ssa : @ir.HotLocalSsa,
  label_used : @bitset.BitSet,
  node_id : Int32,
  states : Array[Int32],
  allocations : Array[Int32],
  depth : Int32,
) -> PrecomputeKnownHeapValue? {
  if depth > 64 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  match precompute_exact_const_instr(ctx, func, node_id) {
    Some(instr) => return Some(KnownEmit(instr))
    None => ()
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.RefFunc(func_idx)) =>
      return Some(KnownEmit(@lib.Instruction::ref_func(func_idx)))
    Some(@lib.StructNew(type_idx)) => {
      let fields = match ctx.module_ctx {
        Some(module_ctx) =>
          match @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
            Some(fields) => fields
            None => return None
          }
        None => return None
      }
      if @ir.hot_child_count(func, node_id) != fields.length() {
        return None
      }
      let values : Array[PrecomputeKnownHeapValue] = []
      for slot = 0; slot < fields.length(); slot = slot + 1 {
        match
          precompute_propagate_known_heap_value_for_node(
            ctx,
            func,
            ssa,
            label_used,
            @ir.hot_child_get(func, node_id, slot),
            states,
            allocations,
            depth + 1,
          ) {
          Some(value) => values.push(value)
          None => return None
        }
      }
      return Some(KnownStructFields(type_idx, values))
    }
    Some(@lib.StructNewDefault(type_idx)) =>
      return Some(KnownStructDefault(type_idx))
    Some(@lib.ArrayNewFixed(type_idx, @lib.U32(length))) => {
      if !precompute_fresh_array_length_is_interpretable(length) ||
        @ir.hot_child_count(func, node_id) != length.reinterpret_as_int() {
        return None
      }
      let values : Array[PrecomputeKnownHeapValue] = []
      for slot = 0; slot < length.reinterpret_as_int(); slot = slot + 1 {
        match
          precompute_propagate_known_heap_value_for_node(
            ctx,
            func,
            ssa,
            label_used,
            @ir.hot_child_get(func, node_id, slot),
            states,
            allocations,
            depth + 1,
          ) {
          Some(value) => values.push(value)
          None => return None
        }
      }
      return Some(KnownArrayFixed(type_idx, values))
    }
    Some(@lib.ArrayNew(type_idx)) if @ir.hot_child_count(func, node_id) == 2 => {
      let value = match
        precompute_propagate_known_heap_value_for_node(
          ctx,
          func,
          ssa,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          allocations,
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      let length = match
        precompute_i32_exact_const(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 1),
        ) {
        Some(@lib.I32(length)) if length >= 0 &&
          precompute_fresh_array_length_is_interpretable(
            length.reinterpret_as_uint(),
          ) => length.reinterpret_as_uint()
        _ => return None
      }
      return Some(KnownArrayFill(type_idx, [value], length))
    }
    Some(@lib.ArrayNewDefault(type_idx)) if @ir.hot_child_count(func, node_id) ==
      1 => {
      let length = match
        precompute_i32_exact_const(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 0),
        ) {
        Some(@lib.I32(length)) if length >= 0 &&
          precompute_fresh_array_length_is_interpretable(
            length.reinterpret_as_uint(),
          ) => length.reinterpret_as_uint()
        _ => return None
      }
      return Some(KnownArrayDefault(type_idx, length))
    }
    Some(@lib.StructGet(type_idx, @lib.U32(field)))
    | Some(@lib.StructGetS(type_idx, @lib.U32(field)))
    | Some(@lib.StructGetU(type_idx, @lib.U32(field))) => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return None
      }
      let source = match
        precompute_propagate_known_heap_value_for_node(
          ctx,
          func,
          ssa,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          allocations,
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      let signed = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::struct_get_s(type_idx, @lib.U32(field)))
      let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::struct_get_u(type_idx, @lib.U32(field)))
      return precompute_known_heap_struct_get(
        ctx, source, type_idx, field, signed, unsigned,
      )
    }
    Some(@lib.ArrayGet(type_idx))
    | Some(@lib.ArrayGetS(type_idx))
    | Some(@lib.ArrayGetU(type_idx)) => {
      if @ir.hot_child_count(func, node_id) != 2 {
        return None
      }
      let source = match
        precompute_propagate_known_heap_value_for_node(
          ctx,
          func,
          ssa,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          allocations,
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      let index = match
        precompute_i32_exact_const(
          ctx,
          func,
          @ir.hot_child_get(func, node_id, 1),
        ) {
        Some(@lib.I32(index)) => index.reinterpret_as_uint()
        None => return None
      }
      let signed = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::array_get_s(type_idx))
      let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
        Some(@lib.Instruction::array_get_u(type_idx))
      return precompute_known_heap_array_get(
        ctx, source, type_idx, index, signed, unsigned,
      )
    }
    Some(@lib.ArrayLen) => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return None
      }
      let source = match
        precompute_propagate_known_heap_value_for_node(
          ctx,
          func,
          ssa,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          allocations,
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      return precompute_known_heap_array_len(source)
    }
    Some(@lib.RefCast(nullable, _, target)) => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return None
      }
      let source = match
        precompute_propagate_known_heap_value_for_node(
          ctx,
          func,
          ssa,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          allocations,
          depth + 1,
        ) {
        Some(value) => value
        None => return None
      }
      if precompute_known_heap_value_matches_cast(source, nullable, target) {
        return Some(source)
      }
      return None
    }
    _ => ()
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::RefFunc =>
      Some(
        KnownEmit(
          @lib.Instruction::ref_func(
            @lib.FuncIdx::new(node.imm0.reinterpret_as_uint()),
          ),
        ),
      )
    @ir.HotOp::GlobalGet =>
      match ctx.module_ctx {
        Some(module_ctx) =>
          precompute_known_heap_value_from_global(
            module_ctx,
            @lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
            depth + 1,
          )
        None => None
      }
    @ir.HotOp::LocalGet => {
      let allocation_id = precompute_propagate_allocation_for_node(
        func, ssa, label_used, node_id, states, allocations,
      )
      if allocation_id >= 0 && allocation_id != node_id {
        precompute_propagate_known_heap_value_for_node(
          ctx,
          func,
          ssa,
          label_used,
          allocation_id,
          states,
          allocations,
          depth + 1,
        )
      } else {
        None
      }
    }
    @ir.HotOp::LocalTee if @ir.hot_child_count(func, node_id) == 1 =>
      precompute_propagate_known_heap_value_for_node(
        ctx,
        func,
        ssa,
        label_used,
        @ir.hot_child_get(func, node_id, 0),
        states,
        allocations,
        depth + 1,
      )
    _ => None
  }
}

///|
fn precompute_replace_with_known_emit(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.RefFunc(func_idx) => {
      let type_idx = match ctx.module_ctx {
        Some(module_ctx) =>
          match @ir.hot_module_resolve_func_type_idx(module_ctx, func_idx) {
            Some(type_idx) => type_idx
            None => return false
          }
        None => return false
      }
      let ref_type = @lib.RefType::new(
        false,
        @lib.HeapType::new(type_idx),
        exact=ctx.module_ctx.unwrap().uses_custom_descriptors,
      )
      let temp = @ir.hot_build_ref_func(func, func_idx, ref_type)
      let replacement = @ir.hot_node_get(func, temp)
      @ir.hot_delete_node(func, temp)
      precompute_replace_node_rehoming_control_label(func, node_id, replacement)
      pass_mark_mutated(ctx, func)
      true
    }
    _ => precompute_replace_with_const_instr(ctx, func, node_id, instr)
  }
}

///|
fn precompute_propagate_try_fold_known_heap_read(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  ssa : @ir.HotLocalSsa,
  label_used : @bitset.BitSet,
  node_id : Int32,
  states : Array[Int32],
  allocations : Array[Int32],
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructGet(_, _))
    | Some(@lib.StructGetS(_, _))
    | Some(@lib.StructGetU(_, _))
    | Some(@lib.ArrayGet(_))
    | Some(@lib.ArrayGetS(_))
    | Some(@lib.ArrayGetU(_))
    | Some(@lib.ArrayLen) => ()
    _ => return false
  }
  match
    precompute_propagate_known_heap_value_for_node(
      ctx, func, ssa, label_used, node_id, states, allocations, 0,
    ) {
    Some(KnownEmit(instr)) => {
      let value = match precompute_build_known_emit(ctx, func, instr) {
        Some(value) => value
        None => return false
      }
      let body : Array[Int32] = []
      for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
        let child = @ir.hot_child_get(func, node_id, slot)
        precompute_propagate_append_heap_operand_effects(
          ctx, func, ssa, label_used, child, states, allocations, body, 0,
        )
      }
      body.push(value)
      let temp = if body.length() == 1 {
        value
      } else {
        let result_type = @ir.hot_type_results(
            func,
            @ir.hot_node_type(func, value),
          )[0]
        @ir.hot_build_block(func, @lib.BlockType::val_type(result_type), body)
      }
      let replacement = @ir.hot_node_get(func, temp)
      @ir.hot_delete_node(func, temp)
      precompute_replace_node_rehoming_control_label(func, node_id, replacement)
      pass_mark_mutated(ctx, func)
      true
    }
    _ => false
  }
}

///|
fn precompute_propagate_allocation_for_node(
  func : @ir.HotFunc,
  ssa : @ir.HotLocalSsa,
  label_used : @bitset.BitSet,
  node_id : Int32,
  states : Array[Int32],
  allocations : Array[Int32],
) -> Int32 {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return -1
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.StructNew(_))
    | Some(@lib.StructNewDefault(_))
    | Some(@lib.ArrayNew(_))
    | Some(@lib.ArrayNewDefault(_))
    | Some(@lib.ArrayNewFixed(_, _)) => return node_id
    _ => ()
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::LocalGet => {
      let value_id = ssa.local_get_values[node_id]
      if value_id >= 0 {
        return precompute_propagate_allocation_for_value(
          func, ssa, label_used, value_id, states, allocations,
        )
      }
    }
    @ir.HotOp::LocalTee =>
      if @ir.hot_child_count(func, node_id) == 1 {
        return precompute_propagate_allocation_for_node(
          func,
          ssa,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          allocations,
        )
      }
    @ir.HotOp::Block => {
      let label_id = @ir.hot_control_node_label(func, node_id)
      let body = @ir.hot_region_body_ref(func, node_id)
      let count = @ir.hot_region_root_count(func, body)
      if @ir.hot_node_result_arity(func, node_id) == 1 &&
        !pass_label_is_used(label_id, label_used) &&
        count > 0 {
        return precompute_propagate_allocation_for_node(
          func,
          ssa,
          label_used,
          @ir.hot_region_get(func, body, count - 1),
          states,
          allocations,
        )
      }
    }
    _ => ()
  }
  -1
}

///|
fn precompute_propagate_allocation_for_value(
  func : @ir.HotFunc,
  ssa : @ir.HotLocalSsa,
  label_used : @bitset.BitSet,
  value_id : Int32,
  states : Array[Int32],
  allocations : Array[Int32],
) -> Int32 {
  if value_id < 0 || value_id >= states.length() {
    return -1
  }
  match states[value_id] {
    1 => return -1
    2 => return allocations[value_id]
    _ => ()
  }
  states[value_id] = 1
  let resolved = match @ir.ssa_value_origin(ssa, value_id) {
    @ir.HotSsaValueOrigin::LocalSetDef(node_id)
    | @ir.HotSsaValueOrigin::LocalTeeDef(node_id) =>
      if @ir.hot_child_count(func, node_id) == 1 {
        precompute_propagate_allocation_for_node(
          func,
          ssa,
          label_used,
          @ir.hot_child_get(func, node_id, 0),
          states,
          allocations,
        )
      } else {
        -1
      }
    @ir.HotSsaValueOrigin::PhiDef(phi_id) => {
      let inputs = @ir.ssa_phi_inputs(ssa, phi_id)
      let mut candidate = -1
      let mut ok = inputs.length() > 0
      for input in inputs {
        let allocation = precompute_propagate_allocation_for_value(
          func,
          ssa,
          label_used,
          input.value_id,
          states,
          allocations,
        )
        if allocation < 0 {
          ok = false
        } else if candidate < 0 {
          candidate = allocation
        } else if candidate != allocation {
          ok = false
        }
        if !ok {
          break
        }
      }
      if ok {
        candidate
      } else {
        -1
      }
    }
    @ir.HotSsaValueOrigin::EntryParamDef(_)
    | @ir.HotSsaValueOrigin::EntryDefaultInitDef(_) => -1
  }
  states[value_id] = 2
  allocations[value_id] = resolved
  resolved
}

///|
fn precompute_propagate_mark_loop_carried_default_relations(
  func : @ir.HotFunc,
  node_id : Int32,
  inside_loop : Bool,
  loop_written : Array[Bool],
  get_inside_loop : Array[Bool],
  visited_outside_loop : Array[Bool],
  visited_inside_loop : Array[Bool],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let visited = if inside_loop {
    visited_inside_loop
  } else {
    visited_outside_loop
  }
  if visited[node_id] {
    return
  }
  visited[node_id] = true
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet =>
      if inside_loop && node_id < get_inside_loop.length() {
        get_inside_loop[node_id] = true
      }
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
      if inside_loop && node.imm0 >= 0 && node.imm0 < loop_written.length() {
        loop_written[node.imm0] = true
      }
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    precompute_propagate_mark_loop_carried_default_relations(
      func,
      @ir.hot_child_get(func, node_id, slot),
      inside_loop,
      loop_written,
      get_inside_loop,
      visited_outside_loop,
      visited_inside_loop,
    )
  }
  let visit_region = fn(region_ref, nested_inside_loop) {
    for idx = 0
        idx < @ir.hot_region_root_count(func, region_ref)
        idx = idx + 1 {
      precompute_propagate_mark_loop_carried_default_relations(
        func,
        @ir.hot_region_get(func, region_ref, idx),
        nested_inside_loop,
        loop_written,
        get_inside_loop,
        visited_outside_loop,
        visited_inside_loop,
      )
    }
  }
  match node.op {
    @ir.HotOp::Loop =>
      visit_region(@ir.hot_region_body_ref(func, node_id), true)
    @ir.HotOp::Block =>
      visit_region(@ir.hot_region_body_ref(func, node_id), inside_loop)
    @ir.HotOp::If => {
      visit_region(@ir.hot_region_then_ref(func, node_id), inside_loop)
      match @ir.hot_region_else_ref(func, node_id) {
        Some(region_ref) => visit_region(region_ref, inside_loop)
        None => ()
      }
    }
    @ir.HotOp::Try | @ir.HotOp::TryTable => {
      visit_region(@ir.hot_region_body_ref(func, node_id), inside_loop)
      visit_region(@ir.hot_region_catch_list_ref(func, node_id), inside_loop)
    }
    _ => ()
  }
}

///|
fn precompute_propagate_prefix_fold_local_gets(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> Bool {
  // A node-indexed SSA overlay cannot choose a different origin per repeated
  // producer evaluation. Preserve this phase before it queries that overlay.
  if pass_has_repeated_entry_evaluation(func) {
    return false
  }
  let ssa = pass_require_ssa(ctx, func)
  let label_used = pass_compute_label_used(func)
  // Scalar reaching writes must include control nested in value operands.
  // The root-only SSA graph can miss loop-body copies in those conditions.
  let graph = @ir.local_graph_build(
    func,
    @ir.cfg_build(func, expand_operand_control=true),
  )
  let states = Array::make(@ir.hot_node_count(func), 0)
  let value_consts : Array[@ir.HotConstPayload?] = Array::make(
    @ir.hot_node_count(func),
    None,
  )
  let allocation_states = Array::make(ssa.value_types.length(), 0)
  let allocation_values = Array::make(ssa.value_types.length(), -1)
  let allocation_folds : Array[PrecomputePropagateAllocationFold] = []
  let known_heap_read_folds : Array[Int32] = []
  let identity_folds : Array[PrecomputePropagateIdentityFold] = []
  let result_if_arm_written = Array::make(@ir.hot_local_count(func), false)
  let result_if_direct_get_consts : Array[@ir.HotConstPayload?] = Array::make(
    @ir.hot_node_count(func),
    None,
  )
  let loop_written = Array::make(@ir.hot_local_count(func), false)
  let get_inside_loop = Array::make(@ir.hot_node_count(func), false)
  let visited_outside_loop = Array::make(@ir.hot_node_count(func), false)
  let visited_inside_loop = Array::make(@ir.hot_node_count(func), false)
  for root_idx = 0; root_idx < @ir.hot_root_count(func); root_idx = root_idx + 1 {
    precompute_propagate_mark_loop_carried_default_relations(
      func,
      @ir.hot_root_get(func, root_idx),
      false,
      loop_written,
      get_inside_loop,
      visited_outside_loop,
      visited_inside_loop,
    )
    precompute_propagate_mark_result_if_direct_const_gets(
      ctx,
      func,
      @ir.hot_root_get(func, root_idx),
      result_if_direct_get_consts,
    )
    precompute_propagate_mark_result_if_arm_writes(
      func,
      @ir.hot_root_get(func, root_idx),
      false,
      result_if_arm_written,
    )
  }
  let replacements : Array[PrecomputePropagatePrefixReplacement] = []
  for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
    if !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let is_ref_is_null = match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::RefIsNull => true
      _ => false
    }
    if is_ref_is_null && @ir.hot_child_count(func, node_id) == 1 {
      let allocation = precompute_propagate_allocation_for_node(
        func,
        ssa,
        label_used,
        @ir.hot_child_get(func, node_id, 0),
        allocation_states,
        allocation_values,
      )
      if allocation >= 0 {
        identity_folds.push({ node_id, value: @lib.I32(0), })
      }
    }
    match @ir.hot_node_exact_instr(func, node_id) {
      Some(@lib.StructGet(_, _))
      | Some(@lib.StructGetS(_, _))
      | Some(@lib.StructGetU(_, _))
      | Some(@lib.ArrayGet(_))
      | Some(@lib.ArrayGetS(_))
      | Some(@lib.ArrayGetU(_))
      | Some(@lib.ArrayLen) => {
        known_heap_read_folds.push(node_id)
        if @ir.hot_child_count(func, node_id) > 0 {
          let allocation_id = precompute_propagate_allocation_for_node(
            func,
            ssa,
            label_used,
            @ir.hot_child_get(func, node_id, 0),
            allocation_states,
            allocation_values,
          )
          if allocation_id >= 0 {
            allocation_folds.push({ node_id, allocation_id, })
          }
        }
      }
      Some(@lib.RefEq) =>
        if @ir.hot_child_count(func, node_id) == 2 {
          let lhs = @ir.hot_child_get(func, node_id, 0)
          let rhs = @ir.hot_child_get(func, node_id, 1)
          let lhs_allocation = precompute_propagate_allocation_for_node(
            func, ssa, label_used, lhs, allocation_states, allocation_values,
          )
          let rhs_allocation = precompute_propagate_allocation_for_node(
            func, ssa, label_used, rhs, allocation_states, allocation_values,
          )
          if lhs_allocation >= 0 && rhs_allocation >= 0 {
            identity_folds.push({
              node_id,
              value: @lib.I32(
                if lhs_allocation == rhs_allocation {
                  1
                } else {
                  0
                },
              ),
            })
          }
        }
      _ => ()
    }
  }
  let mut changed = false
  for fold in allocation_folds {
    if !@ir.hot_node_is_live(func, fold.node_id) {
      continue
    }
    if precompute_try_fold_fresh_struct_get_from_allocation(
        ctx,
        func,
        fold.node_id,
        fold.allocation_id,
      ) ||
      precompute_try_fold_fresh_array_len_from_allocation(
        ctx,
        func,
        fold.node_id,
        fold.allocation_id,
      ) ||
      precompute_try_fold_fresh_array_get_from_allocation(
        ctx,
        func,
        fold.node_id,
        fold.allocation_id,
      ) {
      changed = true
    }
  }
  for node_id in known_heap_read_folds {
    if @ir.hot_node_is_live(func, node_id) &&
      precompute_propagate_try_fold_known_heap_read(
        ctx, func, ssa, label_used, node_id, allocation_states, allocation_values,
      ) {
      changed = true
    }
  }
  for fold in identity_folds {
    if @ir.hot_node_is_live(func, fold.node_id) &&
      precompute_replace_gc_identity_with_const(
        ctx,
        func,
        fold.node_id,
        fold.value,
      ) {
      changed = true
    }
  }
  // Apply evaluator-derived allocation facts before resolving scalar local
  // reads. This still uses the single SSA solve above, but lets dependent local
  // definitions observe constants produced by that same bounded fact solve.
  for node_id = 0; node_id < states.length(); node_id = node_id + 1 {
    if !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::LocalGet => {
        let local_id = @ir.hot_node_get(func, node_id).imm0
        match result_if_direct_get_consts[node_id] {
          Some(payload) => {
            replacements.push({ node_id, payload, })
            continue
          }
          None => ()
        }
        let value_id = ssa.local_get_values[node_id]
        if value_id < 0 {
          continue
        }
        if local_id >= 0 &&
          local_id < loop_written.length() &&
          loop_written[local_id] &&
          get_inside_loop[node_id] &&
          (match @ir.ssa_value_origin(ssa, value_id) {
            @ir.HotSsaValueOrigin::EntryDefaultInitDef(origin_local) =>
              origin_local == local_id
            _ => false
          }) {
          // The same lexical local can be default-valued on the first trip and
          // loop-carried from a nested write on later trips. Until every such
          // backedge receives a real SSA phi, retain the dynamic read.
          continue
        }
        if local_id >= 0 &&
          local_id < result_if_arm_written.length() &&
          result_if_arm_written[local_id] {
          // A real phi proves CFG/SSA observed both result-if arms. If SSA
          // reports a stale entry or prior definition instead, retain the get.
          match @ir.ssa_value_origin(ssa, value_id) {
            @ir.HotSsaValueOrigin::PhiDef(_) => ()
            _ => continue
          }
        }
        // Outside result-producing if arms, trust the SSA origin directly.
        // In particular, an entry-default read before a later write is a real
        // constant, even when that local is written elsewhere in the function.
        match
          precompute_propagate_prefix_const_for_node(
            ctx, func, graph, label_used, node_id, states, value_consts,
          ) {
          Some(payload) => replacements.push({ node_id, payload, })
          None => ()
        }
      }
      _ => ()
    }
  }
  for replacement in replacements {
    if !@ir.hot_node_is_live(func, replacement.node_id) {
      continue
    }
    let temp = match replacement.payload {
      @ir.HotConstPayload::I32Const(value) =>
        @ir.hot_build_const_i32(func, value)
      @ir.HotConstPayload::I64Const(value) =>
        @ir.hot_build_const_i64(func, value)
      @ir.HotConstPayload::F32Const(value) =>
        @ir.hot_build_const_f32(func, value)
      @ir.HotConstPayload::F64Const(value) =>
        @ir.hot_build_const_f64(func, value)
      @ir.HotConstPayload::RefNullConst(value) =>
        @ir.hot_build_ref_null(func, value)
      @ir.HotConstPayload::StringConst(value) =>
        @ir.hot_build_string_const(func, value)
    }
    let next_node = @ir.hot_node_get(func, temp)
    @ir.hot_delete_node(func, temp)
    precompute_replace_node_rehoming_control_label(
      func,
      replacement.node_id,
      next_node,
    )
    pass_mark_mutated(ctx, func)
    changed = true
  }
  changed
}

///|
pub fn precompute_propagate_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  // Resolve the local graph before mutating control. The current plain
  // precompute cleanup can temporarily leave detached label owners until
  // lowering; requiring SSA after that mutation would verify the transient
  // state. Applying the proven local facts first still preserves Binaryen's
  // bounded contract: one local solve followed by one evaluator run.
  let propagated = precompute_propagate_prefix_fold_local_gets(ctx, func)
  let precomputed = precompute_run(ctx, func)
  if propagated || precomputed.changed {
    HotPassResult::changed()
  } else {
    HotPassResult::unchanged()
  }
}

///|
fn precompute_propagate_append_heap_operand_effects(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  ssa : @ir.HotLocalSsa,
  label_used : @bitset.BitSet,
  node_id : Int32,
  states : Array[Int32],
  allocations : Array[Int32],
  out : Array[Int32],
  depth : Int32,
) -> Unit {
  if precompute_is_discardable_value(func, node_id) {
    return
  }
  let known_read = match @ir.hot_node_exact_instr(func, node_id) {
    Some(
      @lib.StructGet(_, _)
      | @lib.StructGetS(_, _)
      | @lib.StructGetU(_, _)
      | @lib.ArrayGet(_)
      | @lib.ArrayGetS(_)
      | @lib.ArrayGetU(_)
      | @lib.ArrayLen
    ) => true
    _ => false
  }
  if depth < 64 &&
    known_read &&
    precompute_propagate_known_heap_value_for_node(
      ctx, func, ssa, label_used, node_id, states, allocations, 0,
    )
    is Some(_) {
    // Known immutable reads prove the dereference/bounds; only operands remain observable.
    for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
      precompute_propagate_append_heap_operand_effects(
        ctx,
        func,
        ssa,
        label_used,
        @ir.hot_child_get(func, node_id, slot),
        states,
        allocations,
        out,
        depth + 1,
      )
    }
  } else {
    out.push(@ir.hot_build_drop(func, node_id))
  }
}

///|
// Integer arithmetic retains the sticky low bits that an intermediate f64
// would lose at an f32 halfway boundary. Round once, ties to even.
fn precompute_u64_to_f32(value : UInt64, negative~ : Bool = false) -> Float {
  if value == 0UL {
    return 0.0F
  }
  let mut exponent = 63 - value.clz()
  let mut significand = if exponent <= 23 {
    value << (23 - exponent)
  } else {
    let shift = exponent - 23
    let high = value >> shift
    let remainder = value & ((1UL << shift) - 1UL)
    let halfway = 1UL << (shift - 1)
    if remainder > halfway || (remainder == halfway && (high & 1UL) != 0UL) {
      high + 1UL
    } else {
      high
    }
  }
  if significand == 0x1000000UL {
    significand = significand >> 1
    exponent += 1
  }
  let sign = if negative { 0x80000000U } else { 0U }
  let bits = sign |
    ((exponent + 127).to_uint() << 23) |
    (significand.to_uint() & 0x7fffffU)
  bits.reinterpret_as_int().reinterpret_as_float()
}

///|
fn precompute_heap_identity_has_alternative_exit(
  func : @ir.HotFunc,
  node_id : Int32,
  label : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  if @ir.hot_branch_target(func, node_id) == Some(label) ||
    @ir.hot_branch_table_targets_for_node(func, node_id).contains(label) {
    return true
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Try | @ir.HotOp::TryTable => return true
    @ir.HotOp::Continuation =>
      if @ir.hot_continuation_targets(func, node_id).contains(label) {
        return true
      }
    _ => ()
  }
  for slot in 0..<@ir.hot_child_count(func, node_id) {
    if precompute_heap_identity_has_alternative_exit(
        func,
        @ir.hot_child_get(func, node_id, slot),
        label,
      ) {
      return true
    }
  }
  false
}

///|
// Scalar constants have one opcode and a signed LEB; splats have two opcode
// bytes. No per-node module encoding is needed to compare them with 18 bytes.
fn precompute_materialization_sleb_size(input : Int64) -> Int32 {
  let mut value = input
  let mut size = 1
  while value < -64L || value > 63L {
    value = value >> 7
    size += 1
  }
  size
}

///|
fn precompute_materialization_repeated(
  bytes : Array[Byte],
  width : Int32,
) -> Bool {
  for index in width..<16 {
    if bytes[index] != bytes[index % width] {
      return false
    }
  }
  true
}

///|
fn precompute_materialize_vector(
  instr : @lib.Instruction,
) -> Array[@lib.Instruction]? {
  let bytes = match precompute_v128_const_bytes(instr) {
    Some(bytes) => bytes
    None => return None
  }
  let mut best_size = 18
  let mut best : Array[@lib.Instruction]? = None
  for width in [1, 2, 4, 8] {
    if !precompute_materialization_repeated(bytes, width) {
      continue
    }
    let bits = precompute_v128_read_u64(bytes, 0)
    let value = match width {
      1 => ((bytes[0].to_uint() << 24).reinterpret_as_int() >> 24).to_int64()
      2 => ((bits.to_uint() << 16).reinterpret_as_int() >> 16).to_int64()
      4 => bits.to_uint().reinterpret_as_int().to_int64()
      _ => bits.reinterpret_as_int64()
    }
    let size = 3 + precompute_materialization_sleb_size(value)
    if size < best_size {
      best_size = size
      best = Some(
        match width {
          1 =>
            [
              @lib.Instruction::i32_const(@lib.I32(value.to_int())),
              @lib.Instruction::i8x16_splat(),
            ]
          2 =>
            [
              @lib.Instruction::i32_const(@lib.I32(value.to_int())),
              @lib.Instruction::i16x8_splat(),
            ]
          4 =>
            [
              @lib.Instruction::i32_const(@lib.I32(value.to_int())),
              @lib.Instruction::i32x4_splat(),
            ]
          _ =>
            [
              @lib.Instruction::i64_const(@lib.I64(value)),
              @lib.Instruction::i64x2_splat(),
            ]
        },
      )
    }
    // f32 costs seven bytes and can beat a five-byte signed i32 immediate.
    if width == 4 && 7 < best_size {
      best_size = 7
      best = Some([
        @lib.Instruction::f32_const(
          @lib.F32(Float::reinterpret_from_uint(bits.to_uint())),
        ),
        @lib.Instruction::f32x4_splat(),
      ])
    }
    // f64 costs eleven and can beat a nine/ten-byte signed i64 immediate.
    // Reinterpret only: retain every NaN payload and signed-zero bit.
    if width == 8 && 11 < best_size {
      best_size = 11
      best = Some([
        @lib.Instruction::f64_const(@lib.F64(bits.reinterpret_as_double())),
        @lib.Instruction::f64x2_splat(),
      ])
    }
  }
  best
}

///|
fn precompute_materialize_vector_children(
  instr : @lib.Instruction,
) -> @lib.Instruction? {
  match instr {
    @lib.Block(bt, @lib.Expr(body)) =>
      match precompute_materialize_vector_body(body) {
        Some(next) => Some(@lib.Instruction::block(bt, @lib.Expr::new(next)))
        None => None
      }
    @lib.Loop(bt, @lib.Expr(body)) =>
      match precompute_materialize_vector_body(body) {
        Some(next) => Some(@lib.Instruction::loop_(bt, @lib.Expr::new(next)))
        None => None
      }
    @lib.TryTable(bt, catches, @lib.Expr(body)) =>
      match precompute_materialize_vector_body(body) {
        Some(next) =>
          Some(@lib.Instruction::try_table(bt, catches, @lib.Expr::new(next)))
        None => None
      }
    @lib.If(bt, yes, no) => {
      let next_yes = precompute_materialize_vector_body(yes)
      let next_no = match no {
        Some(body) => precompute_materialize_vector_body(body)
        None => None
      }
      if next_yes is None && next_no is None {
        return None
      }
      let yes = match next_yes {
        Some(next) => next
        None => yes
      }
      let no = match no {
        Some(body) =>
          Some(
            match next_no {
              Some(next) => next
              None => body
            },
          )
        None => None
      }
      Some(@lib.Instruction::if_(bt, yes, no))
    }
    @lib.Try(bt, expr, catches, delegate) => {
      let @lib.Expr(body) = expr
      let next_body = precompute_materialize_vector_body(body)
      let mut next_catches : Array[@lib.LegacyCatch]? = None
      for index in 0.. child
        }
        match precompute_materialize_vector_body(child) {
          Some(next) => {
            if next_catches is None {
              next_catches = Some(catches.copy())
            }
            next_catches.unwrap()[index] = match catches[index] {
              @lib.LegacyCatch(tag, _) =>
                @lib.LegacyCatch::tagged(tag, @lib.Expr::new(next))
              @lib.LegacyCatchAll(_) =>
                @lib.LegacyCatch::all(@lib.Expr::new(next))
            }
          }
          None => ()
        }
      }
      if next_body is None && next_catches is None {
        return None
      }
      let expr = match next_body {
        Some(next) => @lib.Expr::new(next)
        None => expr
      }
      let catches = match next_catches {
        Some(next) => next
        None => catches
      }
      Some(@lib.Instruction::try_(bt, expr, catches, delegate~))
    }
    _ => None
  }
}

///|
// None means unchanged: scalar leaves and unchanged control bodies need no
// result tuple, mutable capture, callback, or replacement control wrapper.
fn precompute_materialize_vector_body(
  body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
  let mut out : Array[@lib.Instruction]? = None
  for index in 0.. next
      None => original
    }
    let splat = precompute_materialize_vector(instr)
    if nested is Some(_) || splat is Some(_) {
      if out is None {
        let prefix : Array[@lib.Instruction] = Array::new(
          capacity=body.length() + 1,
        )
        for previous in 0..
        match splat {
          Some(pair) =>
            for item in pair {
              items.push(item)
            }
          None => items.push(instr)
        }
      None => ()
    }
  }
  out
}

///|
// Run after folding and writeback repair, so vector users see literals while
// folding. Apply the smaller final representation only under a size policy.
fn precompute_materialize_vectors_for_size(
  mod_ : @lib.Module,
  shrink_level : Int32,
  touched~ : Array[Bool]? = None,
) -> @lib.Module {
  if shrink_level <= 0 || pass_module_has_offset_sensitive_metadata(mod_) {
    return mod_
  }
  let funcs = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => return mod_
  }
  let mut next : Array[@lib.Func]? = None
  for index in 0..= selected.length() || !selected[index]) {
      continue
    }
    let @lib.Func(locals, @lib.Expr(body)) = funcs[index]
    match precompute_materialize_vector_body(body) {
      Some(body) => {
        if next is None {
          next = Some(funcs.copy())
        }
        next.unwrap()[index] = @lib.Func::new(locals, @lib.Expr::new(body))
      }
      None => ()
    }
  }
  let next = match next {
    Some(next) => next
    None => return mod_
  }
  let candidate = mod_.with_code_sec(@lib.CodeSec::new(next))
  let baseline = if mod_.compiler_fact_custom_section is Some(_) {
    mod_.without_compiler_fact_custom_section()
  } else {
    mod_
  }
  // One full-module check includes body/section framing and all retained names.
  match @binary.encoded_module_sizes(baseline, candidate) {
    Ok((before, after)) if after < before => candidate
    _ => mod_
  }
}

///|
fn precompute_materialize_stack_vectors_for_size(
  mod_ : @lib.Module,
  descriptors : Array[@ir.HotPassDescriptor],
  shrink_level : Int32,
  touched~ : Array[Bool]? = None,
) -> @lib.Module {
  if shrink_level <= 0 {
    return mod_
  }
  for descriptor in descriptors {
    if descriptor.name == "precompute" ||
      descriptor.name == "precompute-propagate" {
      return precompute_materialize_vectors_for_size(
        mod_,
        shrink_level,
        touched~,
      )
    }
  }
  mod_
}