///|
pub enum HotVerifyError {
  InvalidBodyResultType(TypeId)
  MissingTypeKey(TypeId, String)
  MismatchedTypeKey(TypeId, String, TypeId)
  InvalidRoot(Int32, NodeId)
  InvalidNodeType(NodeId, TypeId)
  InvalidNodeFlags(NodeId, UInt32, UInt32)
  InvalidChildSpan(NodeId, Int32, Int32)
  InvalidChildRef(NodeId, Int32, NodeId)
  InvalidLabelResultType(LabelId, TypeId)
  InvalidLabelOwner(LabelId, NodeId)
  InvalidLabelOwnerKind(LabelId, HotLabelKind, HotOp)
  InvalidControlLabel(NodeId, LabelId)
  InvalidSideTableRef(NodeId, HotSideTableKind, SideTableId)
  InvalidControlChildCount(NodeId, Int32, Int32)
  InvalidControlChild(NodeId, Int32, NodeId)
  InvalidCatchPayload(NodeId, String)
  InvalidBranchTarget(NodeId, LabelId)
  InvalidBranchArity(NodeId, LabelId, Int32, Int32)
  InvalidCfgEntry(BlockId)
  InvalidCfgExit(BlockId)
  InvalidCfgBlockId(BlockId, BlockId)
  InvalidCfgEdge(BlockId, CfgEdge)
  InvalidCfgEdgeSymmetry(BlockId, CfgEdge)
  InvalidCfgNodeMapping(NodeId, BlockId)
  InvalidCfgBlockNode(BlockId, NodeId)
  InvalidCfgRegionRoot(NodeId, Int32, BlockId)
  InvalidCfgShape(String, Int32, Int32)
  InvalidDomEntry(BlockId, BlockId)
  InvalidDomShape(String, Int32, Int32)
  InvalidDomReachable(BlockId)
  InvalidDomIdom(BlockId, BlockId)
  InvalidDomChild(BlockId, BlockId)
  InvalidDomFrontier(BlockId, BlockId)
  InvalidSsaRevision(Int32, Int32)
  InvalidSsaShape(String, Int32, Int32)
  InvalidSsaValue(SsaValueId)
  InvalidSsaPhi(PhiId)
  InvalidSsaLocal(LocalId)
  InvalidSsaNode(NodeId)
  InvalidSsaValueOrigin(SsaValueId, HotSsaValueOrigin)
  InvalidSsaPhiInput(PhiId, BlockId, SsaValueId)
  InvalidSsaPhiAlignment(PhiId, BlockId, Int32, Int32)
  InvalidSsaType(SsaValueId, ValType, ValType)
  InvalidSsaUse(SsaValueId, HotSsaUseOrigin)
  InvalidSsaDominance(SsaValueId, BlockId, BlockId)
  InvalidAnalysisOwner
  InvalidAnalysisRevision(HotAnalysis, Int32, Int32)
  InvalidAnalysisDependency(HotAnalysis, HotAnalysis)
} derive(Eq, Debug)

///|
fn hot_verify_type_id_is_valid(func : HotFunc, ty : TypeId) -> Bool {
  ty >= 0 && ty < func.types.length()
}

///|
fn hot_verify_label_id_is_valid(func : HotFunc, label_id : LabelId) -> Bool {
  hot_label_is_live(func, label_id)
}

///|
fn hot_verify_side_table_id_is_valid(
  func : HotFunc,
  kind : HotSideTableKind,
  side_table_id : SideTableId,
) -> Bool {
  if side_table_id < 0 {
    return false
  }
  match kind {
    HotSideTableKind::ConstPayloadTable => side_table_id < func.consts.length()
    HotSideTableKind::MemArgTable => side_table_id < func.memargs.length()
    HotSideTableKind::BranchTableTable =>
      side_table_id < func.branch_tables.length()
    HotSideTableKind::CatchInfoTable => side_table_id < func.catches.length()
    HotSideTableKind::CallSigTable => side_table_id < func.call_sigs.length()
    HotSideTableKind::InstrPayloadTable =>
      side_table_id < func.instr_payloads.length()
  }
}

///|
fn hot_verify_region_holder(func : HotFunc, node_id : NodeId) -> Bool {
  if !hot_node_is_live(func, node_id) {
    return false
  }
  let node = hot_node_get(func, node_id)
  if node.op != HotOp::Block {
    return false
  }
  let label_id = hot_control_node_label(func, node_id)
  hot_verify_label_id_is_valid(func, label_id) &&
  hot_label_kind(func, label_id) == HotLabelKind::RegionLabel
}

///|
fn hot_verify_branch_payload_arity(func : HotFunc, node_id : NodeId) -> Int32? {
  let node = hot_node_get(func, node_id)
  match node.op {
    HotOp::Br | HotOp::Delegate => Some(node.child_count)
    HotOp::BrOnNonNull =>
      if node.child_count == 0 {
        None
      } else {
        Some(node.child_count)
      }
    HotOp::BrOnCast | HotOp::BrOnCastFail =>
      if node.child_count == 0 {
        None
      } else {
        // Descriptor casts carry an additional descriptor guard after the
        // tested reference; only the reference contributes to branch arity.
        match hot_node_exact_instr(func, node_id) {
          Some(@lib.BrOnCastDescEq(_, _, _))
          | Some(@lib.BrOnCastDescEqFail(_, _, _)) =>
            if node.child_count < 2 {
              None
            } else {
              Some(node.child_count - 1)
            }
          _ => Some(node.child_count)
        }
      }
    HotOp::BrIf | HotOp::BrTable | HotOp::BrOnNull =>
      if node.child_count == 0 {
        None
      } else {
        Some(node.child_count - 1)
      }
    _ => Some(0)
  }
}

///|
fn hot_verify_control_label_kind(
  func : HotFunc,
  node_id : NodeId,
  node : HotNode,
) -> Result[Unit, HotVerifyError] {
  let label_id = hot_control_node_label(func, node_id)
  if !hot_verify_label_id_is_valid(func, label_id) {
    return Err(HotVerifyError::InvalidControlLabel(node_id, label_id))
  }
  let expected = match node.op {
    HotOp::Block => None
    HotOp::Loop => Some(HotLabelKind::LoopLabel)
    HotOp::If => Some(HotLabelKind::IfLabel)
    HotOp::Try => Some(HotLabelKind::TryLabel)
    HotOp::TryTable => Some(HotLabelKind::TryTableLabel)
    _ => None
  }
  match expected {
    Some(kind) if hot_label_kind(func, label_id) != kind =>
      Err(HotVerifyError::InvalidLabelOwnerKind(label_id, kind, node.op))
    _ => Ok(())
  }
}

///|
fn hot_verify_branch_target(
  func : HotFunc,
  node_id : NodeId,
  target : LabelId,
  actual_arity : Int32,
) -> Result[Unit, HotVerifyError] {
  if target != HOT_IMPLICIT_FUNCTION_LABEL &&
    !hot_verify_label_id_is_valid(func, target) {
    return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
  }
  let expected_arity = if hot_node_get(func, node_id).op == HotOp::Delegate {
    // A legacy delegate transfers an exception to the enclosing handler.
    // It does not branch with the target label's ordinary result values.
    0
  } else if target == HOT_IMPLICIT_FUNCTION_LABEL {
    hot_type_result_arity(func, hot_body_result_type(func))
  } else {
    hot_label_branch_arity(func, target)
  }
  if actual_arity != expected_arity {
    return Err(
      HotVerifyError::InvalidBranchArity(
        node_id, target, actual_arity, expected_arity,
      ),
    )
  }
  Ok(())
}

///|
fn hot_verify_branch_targets(
  func : HotFunc,
  node_id : NodeId,
  targets : Array[LabelId],
  actual_arity : Int32,
) -> Result[Unit, HotVerifyError] {
  for target in targets {
    match hot_verify_branch_target(func, node_id, target, actual_arity) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  Ok(())
}

///|
pub fn hot_verify_core(func : HotFunc) -> Result[Unit, HotVerifyError] {
  hot_verify_core_impl(func)
}

///|
fn hot_verify_core_impl(
  func : HotFunc,
  postorder~ : Array[NodeId]? = None,
) -> Result[Unit, HotVerifyError] {
  if !hot_verify_type_id_is_valid(func, hot_body_result_type(func)) {
    return Err(
      HotVerifyError::InvalidBodyResultType(hot_body_result_type(func)),
    )
  }

  for type_id = 0; type_id < func.types.length(); type_id = type_id + 1 {
    let key = func.types[type_id].to_string()
    match func.type_ids.get(key) {
      Some(mapped) if mapped == type_id => ()
      Some(mapped) =>
        return Err(HotVerifyError::MismatchedTypeKey(type_id, key, mapped))
      None => return Err(HotVerifyError::MissingTypeKey(type_id, key))
    }
  }

  for root_idx = 0; root_idx < func.roots.length(); root_idx = root_idx + 1 {
    let root_id = func.roots[root_idx]
    if !hot_node_is_live(func, root_id) {
      return Err(HotVerifyError::InvalidRoot(root_idx, root_id))
    }
  }

  for label_id = 0; label_id < func.labels.length(); label_id = label_id + 1 {
    if !hot_label_is_live(func, label_id) {
      continue
    }
    let label = func.labels[label_id]
    if !hot_verify_type_id_is_valid(func, label.result_type) {
      return Err(
        HotVerifyError::InvalidLabelResultType(label_id, label.result_type),
      )
    }
    if !hot_node_is_live(func, label.owner) {
      return Err(HotVerifyError::InvalidLabelOwner(label_id, label.owner))
    }
    let owner = hot_node_get(func, label.owner)
    if hot_control_node_label(func, label.owner) != label_id {
      return Err(HotVerifyError::InvalidLabelOwner(label_id, label.owner))
    }
    match label.kind {
      HotLabelKind::BlockLabel if owner.op != HotOp::Block =>
        return Err(
          HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
        )
      HotLabelKind::LoopLabel if owner.op != HotOp::Loop =>
        return Err(
          HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
        )
      HotLabelKind::IfLabel if owner.op != HotOp::If =>
        return Err(
          HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
        )
      HotLabelKind::TryLabel if owner.op != HotOp::Try =>
        return Err(
          HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
        )
      HotLabelKind::TryTableLabel if owner.op != HotOp::TryTable =>
        return Err(
          HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
        )
      HotLabelKind::RegionLabel if owner.op != HotOp::Block =>
        return Err(
          HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
        )
      _ => ()
    }
  }

  for node_id = 0; node_id < func.nodes.length(); node_id = node_id + 1 {
    if !hot_node_is_live(func, node_id) {
      continue
    }
    let node = func.nodes[node_id]
    if !hot_verify_type_id_is_valid(func, node.ty) {
      return Err(HotVerifyError::InvalidNodeType(node_id, node.ty))
    }
    let expected_flags = hot_flags_for_exact_payload(func, node.op, node.imm0)
    if node.flags != expected_flags {
      return Err(
        HotVerifyError::InvalidNodeFlags(node_id, node.flags, expected_flags),
      )
    }
    if node.child_base < 0 ||
      node.child_count < 0 ||
      node.child_base + node.child_count > func.children.length() {
      return Err(
        HotVerifyError::InvalidChildSpan(
          node_id,
          node.child_base,
          node.child_count,
        ),
      )
    }
    for slot = 0; slot < node.child_count; slot = slot + 1 {
      let child_id = func.children[node.child_base + slot]
      if child_id == HOT_NONE_NODE {
        continue
      }
      if !hot_node_is_live(func, child_id) {
        return Err(HotVerifyError::InvalidChildRef(node_id, slot, child_id))
      }
    }
    match hot_side_table_kind_for_op(node.op) {
      Some(kind) => {
        let skip_side_table = match node.op {
          HotOp::Atomic =>
            if node.imm1 >= 0 && node.imm1 < func.instr_payloads.length() {
              match hot_instr_payload_get(func, node.imm1).instr {
                Instruction::AtomicFence(_)
                | Instruction::WaitqueueNotify
                | Instruction::StructWait(_, _)
                | Instruction::StructAtomicSet(_, _, _)
                | Instruction::ArrayAtomicSet(_, _)
                | Instruction::StructAtomicGet(_, _, _)
                | Instruction::StructAtomicGetS(_, _, _)
                | Instruction::StructAtomicGetU(_, _, _)
                | Instruction::StructAtomicRmw(_, _, _, _)
                | Instruction::StructAtomicCmpxchg(_, _, _)
                | Instruction::ArrayAtomicGet(_, _)
                | Instruction::ArrayAtomicGetS(_, _)
                | Instruction::ArrayAtomicGetU(_, _)
                | Instruction::ArrayAtomicRmw(_, _, _)
                | Instruction::ArrayAtomicCmpxchg(_, _) => true
                _ => false
              }
            } else {
              false
            }
          _ => false
        }
        if !skip_side_table {
          let side_table_id = if node.op == HotOp::TryTable ||
            node.op == HotOp::Continuation {
            node.imm1
          } else {
            node.imm0
          }
          if !hot_verify_side_table_id_is_valid(func, kind, side_table_id) {
            return Err(
              HotVerifyError::InvalidSideTableRef(node_id, kind, side_table_id),
            )
          }
        }
      }
      None => ()
    }
    if !hot_verify_node_exact_instr_payload(func, node_id) {
      let payload_id = match hot_node_exact_instr_payload_id(func, node_id) {
        Some(id) => id
        None => -1
      }
      return Err(
        HotVerifyError::InvalidSideTableRef(
          node_id,
          HotSideTableKind::InstrPayloadTable,
          payload_id,
        ),
      )
    }
  }

  hot_verify_child_graph(func, postorder)
}

///|
// Label backedges are not child edges. The owned expression graph must remain
// acyclic, including live detached nodes that later edits can reattach.
fn hot_verify_child_graph(
  func : HotFunc,
  postorder : Array[NodeId]?,
) -> Result[Unit, HotVerifyError] {
  // Core admission has checked every live node span and child reference.
  // This traversal only reads the arena, including live detached nodes.
  let marks = Array::make(hot_node_count(func), b'\x00')
  let nodes : Array[NodeId] = []
  let slots : Array[Int32] = []
  for start in 0.. HotVerifyLabelScopes {
  let mut levels = 1
  let mut size = count
  while size > 1 {
    levels += 1
    size = size / 2 + size % 2
  }
  {
    parents: Array::make(count, -1),
    depths: Array::make(count, -1),
    levels,
    jumps: None,
  }
}

///|
// Ordinary trees need no jump storage. A shared node with different enclosing
// scopes uses their nearest common scope, with bounded logarithmic merge work.
fn HotVerifyLabelScopes::jump(
  self : HotVerifyLabelScopes,
  label : LabelId,
  level : Int32,
) -> LabelId {
  if label < 0 {
    return -1
  }
  if level == 0 {
    return self.parents[label]
  }
  let rows = match self.jumps {
    Some(rows) => rows
    None => {
      let rows = Array::make(self.parents.length() * self.levels, -3)
      self.jumps = Some(rows)
      rows
    }
  }
  let key = label * self.levels + level
  if rows[key] == -3 {
    let halfway = self.jump(label, level - 1)
    rows[key] = self.jump(halfway, level - 1)
  }
  rows[key]
}

///|
fn HotVerifyLabelScopes::common(
  self : HotVerifyLabelScopes,
  first : LabelId,
  second : LabelId,
) -> LabelId {
  if first == second || first == -3 {
    return second
  }
  if second == -3 {
    return first
  }
  if first < 0 || second < 0 {
    return -1
  }
  let mut left = first
  let mut right = second
  if self.depths[left] < self.depths[right] {
    left = second
    right = first
  }
  let mut difference = self.depths[left] - self.depths[right]
  let mut level = 0
  while difference > 0 {
    if difference % 2 == 1 {
      left = self.jump(left, level)
    }
    difference /= 2
    level += 1
  }
  if left == right {
    return left
  }
  for power = self.levels - 1; power >= 0; power = power - 1 {
    let lhs = self.jump(left, power)
    let rhs = self.jump(right, power)
    if lhs != rhs {
      left = lhs
      right = rhs
    }
  }
  self.parents[left]
}

///|
fn hot_verify_scope_body_slot(
  func : HotFunc,
  node : HotNode,
  slot : Int32,
) -> Bool {
  match node.op {
    HotOp::Block => slot >= node.imm1
    HotOp::Loop => slot >= hot_label_branch_arity(func, node.imm0)
    HotOp::If => slot == 1 || slot == 2
    HotOp::Try | HotOp::TryTable => true
    _ => false
  }
}

///|
fn hot_verify_branch_scopes(
  func : HotFunc,
  postorder : Array[NodeId],
) -> Result[Unit, HotVerifyError] {
  // Successful core verification supplies a live postorder and valid spans.
  // No node or child storage changes while lexical scopes are checked.
  let contexts = Array::make(hot_node_count(func), -3)
  for root in func.roots {
    contexts[root] = -1
  }
  let scopes = HotVerifyLabelScopes::new(func.labels.length())
  // Reverse child postorder processes every predecessor before a shared node.
  // Its context is the intersection of its incoming lexical scope chains.
  for index = postorder.length() - 1; index >= 0; index = index - 1 {
    let node_id = postorder[index]
    let context = contexts[node_id]
    if context == -3 {
      continue
    }
    let node = func.nodes[node_id]
    let label = match node.op {
      HotOp::Block | HotOp::Loop | HotOp::If | HotOp::Try | HotOp::TryTable =>
        node.imm0
      _ => -1
    }
    let active = if label >= 0 &&
      hot_label_kind(func, label) != HotLabelKind::RegionLabel {
      scopes.parents[label] = context
      scopes.depths[label] = if context < 0 {
        1
      } else {
        scopes.depths[context] + 1
      }
      label
    } else {
      context
    }
    for slot in 0..= 0 {
      let parent = scopes.parents[label] + 1
      next[label] = heads[parent]
      heads[parent] = label
    }
  }
  let starts = Array::make(count, -1)
  let ends = Array::make(count, -1)
  let stack : Array[Int32] = []
  let mut root = heads[0]
  let mut clock = 0
  while root >= 0 || !stack.is_empty() {
    if stack.is_empty() {
      stack.push(root)
      root = next[root]
    }
    let item = stack.pop().unwrap()
    if item < 0 {
      ends[-item - 1] = clock
      continue
    }
    starts[item] = clock
    clock += 1
    stack.push(-item - 1)
    let mut child = heads[item + 1]
    while child >= 0 {
      stack.push(child)
      child = next[child]
    }
  }
  fn check(node_id : NodeId, target : LabelId) -> Result[Unit, HotVerifyError] {
    if target == HOT_IMPLICIT_FUNCTION_LABEL {
      return Ok(())
    }
    let context = contexts[node_id]
    if func.nodes[node_id].op == HotOp::Delegate && context == target {
      return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
    }
    if context < 0 ||
      starts[target] < 0 ||
      starts[target] > starts[context] ||
      ends[context] > ends[target] {
      return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
    }
    Ok(())
  }
  for node_id in postorder {
    if contexts[node_id] == -3 {
      continue
    }
    let node = func.nodes[node_id]
    match node.op {
      HotOp::BrTable => {
        for target in hot_branch_table_targets_for_node(func, node_id) {
          match check(node_id, target) {
            Ok(_) => ()
            Err(err) => return Err(err)
          }
        }
        match check(node_id, hot_branch_table_default_target(func, node.imm0)) {
          Ok(_) => ()
          Err(err) => return Err(err)
        }
      }
      HotOp::Continuation =>
        for target in hot_branch_table_targets(func, node.imm1) {
          match check(node_id, target) {
            Ok(_) => ()
            Err(err) => return Err(err)
          }
        }
      HotOp::TryTable =>
        for arm in hot_catch_info_get(func, node.imm1).catches {
          let target = match arm {
            HotCatchArm::CatchArm(_, label)
            | HotCatchArm::CatchRefArm(_, label)
            | HotCatchArm::CatchAllArm(label)
            | HotCatchArm::CatchAllRefArm(label) => label
          }
          // try_table catch targets are resolved in the enclosing scope,
          // before the try_table's own label is pushed.
          match check(node_id, target) {
            Ok(_) => ()
            Err(err) => return Err(err)
          }
        }
      HotOp::Br
      | HotOp::BrIf
      | HotOp::BrOnNull
      | HotOp::BrOnNonNull
      | HotOp::BrOnCast
      | HotOp::BrOnCastFail
      | HotOp::Delegate =>
        match check(node_id, node.imm0) {
          Ok(_) => ()
          Err(err) => return Err(err)
        }
      _ => ()
    }
  }
  Ok(())
}

///|
fn hot_verify_catch_payload_layout(
  func : HotFunc,
) -> Result[Unit, HotVerifyError] {
  let admitted = Array::make(func.nodes.length(), false)
  for try_id = 0; try_id < func.nodes.length(); try_id = try_id + 1 {
    if !hot_node_is_live(func, try_id) ||
      hot_query_op(func, try_id) != HotOp::Try {
      continue
    }
    let catch_region = hot_child_get(func, try_id, 1)
    if catch_region == HOT_NONE_NODE ||
      !hot_verify_region_holder(func, catch_region) {
      continue
    }
    let catch_ref = hot_region_body_ref(func, catch_region)
    let mut saw_non_payload = false
    let mut tag = -1
    for slot = 0; slot < hot_region_root_count(func, catch_ref); slot = slot + 1 {
      let root_id = hot_region_get(func, catch_ref, slot)
      if hot_query_op(func, root_id) == HotOp::Catch {
        if saw_non_payload {
          return Err(
            HotVerifyError::InvalidCatchPayload(
              root_id, "typed catch payload must precede every catch-body root",
            ),
          )
        }
        let node = hot_node_get(func, root_id)
        if node.child_count != 0 || hot_node_result_arity(func, root_id) != 1 {
          return Err(
            HotVerifyError::InvalidCatchPayload(
              root_id, "typed catch payload must be a childless scalar value",
            ),
          )
        }
        if tag >= 0 && tag != node.imm0 {
          return Err(
            HotVerifyError::InvalidCatchPayload(
              root_id, "one catch region cannot mix payload tags",
            ),
          )
        }
        if admitted[root_id] {
          return Err(
            HotVerifyError::InvalidCatchPayload(
              root_id, "typed catch payload must have one catch-region entry owner",
            ),
          )
        }
        tag = node.imm0
        admitted[root_id] = true
      } else {
        saw_non_payload = true
      }
    }
  }
  for node_id = 0; node_id < func.nodes.length(); node_id = node_id + 1 {
    if hot_node_is_live(func, node_id) &&
      hot_query_op(func, node_id) == HotOp::Catch &&
      !admitted[node_id] {
      return Err(
        HotVerifyError::InvalidCatchPayload(
          node_id, "typed catch payload must be a leading legacy catch-region root",
        ),
      )
    }
  }
  Ok(())
}

///|
fn hot_verify_explicit_block_prefix(
  func : HotFunc,
  id : NodeId,
  node : HotNode,
) -> Result[Unit, HotVerifyError] {
  if node.imm1 < 0 || node.imm1 > node.child_count {
    return Err(
      HotVerifyError::InvalidControlChildCount(id, node.child_count, node.imm1),
    )
  }
  if node.imm1 == 0 {
    return Ok(())
  }
  let params = match hot_type_get(func, node.ty) {
    BlockResultResolved(_, params, _) => params
    _ => return Err(HotVerifyError::InvalidNodeType(id, node.ty))
  }
  let mut param = 0
  let mut slot = 0
  while slot < node.imm1 {
    let child = hot_child_get(func, id, slot)
    let types = hot_type_results(func, hot_node_type(func, child))
    let mut end = slot + 1
    while end < node.imm1 && hot_child_get(func, id, end) == child {
      end += 1
    }
    let lanes = end - slot
    if types.is_empty() {
      if lanes != 1 {
        return Err(HotVerifyError::InvalidControlChild(id, slot, child))
      }
    } else {
      if lanes > types.length() || param + lanes > params.length() {
        return Err(HotVerifyError::InvalidControlChild(id, slot, child))
      }
      for lane in 0.. env
            None => @validate.empty_env()
          }
          let matches = @validate.Match::matches(actual, expected, env)
          if !matches {
            return Err(
              HotVerifyError::InvalidControlChild(id, slot + lane, child),
            )
          }
        }
      }
      param += lanes
    }
    slot = end
  }
  if param != params.length() {
    return Err(
      HotVerifyError::InvalidControlChildCount(id, param, params.length()),
    )
  }
  Ok(())
}

///|
pub fn hot_verify_control(func : HotFunc) -> Result[Unit, HotVerifyError] {
  hot_verify_control_impl(func)
}

///|
fn hot_verify_control_impl(
  func : HotFunc,
  catch_layout_calls~ : Array[Int32]? = None,
  core_verification_calls~ : Array[Int32]? = None,
) -> Result[Unit, HotVerifyError] {
  match core_verification_calls {
    Some(calls) => calls[0] = calls[0] + 1
    None => ()
  }
  let needs_scopes = func.nodes.any(fn(node) {
    node.op is HotOp::Br ||
    node.op is HotOp::BrIf ||
    node.op is HotOp::BrTable ||
    node.op is HotOp::BrOnNull ||
    node.op is HotOp::BrOnNonNull ||
    node.op is HotOp::BrOnCast ||
    node.op is HotOp::BrOnCastFail ||
    node.op is HotOp::Delegate ||
    node.op is HotOp::Continuation ||
    node.op is HotOp::TryTable
  })
  let order : Array[NodeId] = []
  match
    hot_verify_core_impl(
      func,
      postorder=if needs_scopes { Some(order) } else { None },
    ) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }

  // Core verification above admits all live nodes, spans, and child refs.
  // Keep control-shape and sentinel checks in their original order.
  let mut needs_catch_layout = false
  for node_id = 0; node_id < func.nodes.length(); node_id = node_id + 1 {
    if !hot_node_is_live(func, node_id) {
      continue
    }
    let node = func.nodes[node_id]
    match node.op {
      HotOp::Block | HotOp::Loop | HotOp::If | HotOp::Try | HotOp::TryTable =>
        match hot_verify_control_label_kind(func, node_id, node) {
          Ok(_) => ()
          Err(err) => return Err(err)
        }
      _ => ()
    }

    match node.op {
      HotOp::Block | HotOp::Loop => {
        for slot = 0; slot < node.child_count; slot = slot + 1 {
          let child_id = func.children[node.child_base + slot]
          if child_id == HOT_NONE_NODE {
            return Err(
              HotVerifyError::InvalidControlChild(node_id, slot, child_id),
            )
          }
        }
        if node.op == HotOp::Block {
          match hot_verify_explicit_block_prefix(func, node_id, node) {
            Ok(_) => ()
            Err(err) => return Err(err)
          }
        }
      }
      HotOp::If => {
        let entry_count = match hot_type_get(func, node.ty) {
          BlockResultResolved(_, params, _) => params.length()
          _ => 0
        }
        if node.child_count != 3 + entry_count {
          return Err(
            HotVerifyError::InvalidControlChildCount(
              node_id,
              node.child_count,
              3 + entry_count,
            ),
          )
        }
        let condition_id = func.children[node.child_base]
        if condition_id == HOT_NONE_NODE ||
          !hot_node_is_live(func, condition_id) {
          return Err(
            HotVerifyError::InvalidControlChild(node_id, 0, condition_id),
          )
        }
        for slot in 3.. {
        needs_catch_layout = true
        if node.child_count != 2 {
          return Err(
            HotVerifyError::InvalidControlChildCount(
              node_id,
              node.child_count,
              2,
            ),
          )
        }
        for slot = 0; slot < 2; slot = slot + 1 {
          let child_id = func.children[node.child_base + slot]
          if slot == 1 && child_id == HOT_NONE_NODE {
            continue
          }
          if !hot_verify_region_holder(func, child_id) {
            return Err(
              HotVerifyError::InvalidControlChild(node_id, slot, child_id),
            )
          }
        }
      }
      HotOp::Continuation => {
        for target in hot_continuation_targets(func, node_id) {
          if target != HOT_IMPLICIT_FUNCTION_LABEL &&
            !hot_verify_label_id_is_valid(func, target) {
            return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
          }
        }
        let expected = hot_continuation_handlers(
            hot_node_exact_instr(func, node_id).unwrap(),
          )
          .filter(fn(handler) { handler is @lib.ResumeOnLabel(_, _) })
          .length()
        if expected != hot_continuation_targets(func, node_id).length() {
          return Err(
            HotVerifyError::InvalidCatchPayload(
              node_id, "continuation handler target count",
            ),
          )
        }
      }
      HotOp::Catch => needs_catch_layout = true
      HotOp::TryTable => {
        if node.child_count != 2 {
          return Err(
            HotVerifyError::InvalidControlChildCount(
              node_id,
              node.child_count,
              2,
            ),
          )
        }
        for slot = 0; slot < 2; slot = slot + 1 {
          let child_id = func.children[node.child_base + slot]
          if !hot_verify_region_holder(func, child_id) {
            return Err(
              HotVerifyError::InvalidControlChild(node_id, slot, child_id),
            )
          }
        }
        let catch_info = hot_catch_info_get(func, node.imm1)
        for catch_arm in catch_info.catches {
          let target = match catch_arm {
            HotCatchArm::CatchArm(_, label_id)
            | HotCatchArm::CatchRefArm(_, label_id)
            | HotCatchArm::CatchAllArm(label_id)
            | HotCatchArm::CatchAllRefArm(label_id) => label_id
          }
          if target != HOT_IMPLICIT_FUNCTION_LABEL &&
            !hot_verify_label_id_is_valid(func, target) {
            return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
          }
        }
      }
      HotOp::Br
      | HotOp::BrIf
      | HotOp::BrTable
      | HotOp::BrOnNull
      | HotOp::BrOnNonNull
      | HotOp::BrOnCast
      | HotOp::BrOnCastFail
      | HotOp::Delegate => {
        let actual_arity = match
          hot_verify_branch_payload_arity(func, node_id) {
          Some(arity) => arity
          None =>
            return Err(
              HotVerifyError::InvalidControlChildCount(
                node_id,
                node.child_count,
                1,
              ),
            )
        }
        match node.op {
          HotOp::Br =>
            match
              hot_verify_branch_target(func, node_id, node.imm0, actual_arity) {
              Ok(_) => ()
              Err(err) => return Err(err)
            }
          HotOp::Delegate =>
            match
              hot_verify_branch_target(func, node_id, node.imm0, actual_arity) {
              Ok(_) => ()
              Err(err) => return Err(err)
            }
          HotOp::BrIf
          | HotOp::BrOnNull
          | HotOp::BrOnNonNull
          | HotOp::BrOnCast
          | HotOp::BrOnCastFail => {
            let guard_id = func.children[node.child_base + node.child_count - 1]
            if guard_id == HOT_NONE_NODE || !hot_node_is_live(func, guard_id) {
              return Err(
                HotVerifyError::InvalidControlChild(
                  node_id,
                  node.child_count - 1,
                  guard_id,
                ),
              )
            }
            match
              hot_verify_branch_target(func, node_id, node.imm0, actual_arity) {
              Ok(_) => ()
              Err(err) => return Err(err)
            }
          }
          HotOp::BrTable => {
            let selector_id = func.children[node.child_base +
              node.child_count -
              1]
            if selector_id == HOT_NONE_NODE ||
              !hot_node_is_live(func, selector_id) {
              return Err(
                HotVerifyError::InvalidControlChild(
                  node_id,
                  node.child_count - 1,
                  selector_id,
                ),
              )
            }
            let targets = hot_branch_table_targets_for_node(func, node_id)
            let all_targets = targets.copy()
            all_targets.push(hot_branch_table_default_target(func, node.imm0))
            match
              hot_verify_branch_targets(
                func, node_id, all_targets, actual_arity,
              ) {
              Ok(_) => ()
              Err(err) => return Err(err)
            }
          }
          _ => ()
        }
      }
      _ => ()
    }
  }

  // The layout checker only examines live Try/Catch nodes. This flag comes
  // from the complete control scan, so the remaining check is vacuous when
  // neither operation exists. Live orphan payloads still take the full path.
  if needs_catch_layout {
    match catch_layout_calls {
      Some(calls) => calls[0] = calls[0] + 1
      None => ()
    }
    match hot_verify_catch_payload_layout(func) {
      Ok(_) => ()
      Err(err) => return Err(err)
    }
  }
  if needs_scopes {
    hot_verify_branch_scopes(func, order)
  } else {
    Ok(())
  }
}

///|
fn hot_verify_block_id_is_valid(cfg : HotCfg, block_id : BlockId) -> Bool {
  block_id >= 0 && block_id < cfg_block_count(cfg)
}

///|
fn hot_verify_cfg_edge_has_symmetry(
  cfg : HotCfg,
  from : BlockId,
  edge : CfgEdge,
) -> Bool {
  for pred in cfg_predecessor_edges(cfg, edge.block) {
    if pred.block == from && pred.kind == edge.kind {
      return true
    }
  }
  false
}

///|
fn hot_verify_cfg_pred_has_symmetry(
  cfg : HotCfg,
  to : BlockId,
  edge : CfgEdge,
) -> Bool {
  for succ in cfg_successor_edges(cfg, edge.block) {
    if succ.block == to && succ.kind == edge.kind {
      return true
    }
  }
  false
}

///|
fn hot_verify_cfg_block_contains_node(
  cfg : HotCfg,
  block_id : BlockId,
  node_id : NodeId,
) -> Bool {
  for mapped in cfg_block_get(cfg, block_id).nodes {
    if mapped == node_id {
      return true
    }
  }
  false
}

///|
fn hot_verify_node_child_is_region_slot(node : HotNode, slot : Int32) -> Bool {
  match node.op {
    HotOp::Block => slot >= node.imm1
    HotOp::Loop => true
    HotOp::If => slot == 1 || slot == 2
    HotOp::Try | HotOp::TryTable => slot == 0 || slot == 1
    _ => false
  }
}

///|
fn hot_verify_node_tree_contains(
  func : HotFunc,
  root_id : NodeId,
  needle_id : NodeId,
) -> Bool {
  if root_id == needle_id {
    return true
  }
  if !hot_node_is_live(func, root_id) {
    return false
  }
  let root = hot_node_get(func, root_id)
  for slot = 0; slot < root.child_count; slot = slot + 1 {
    if hot_verify_node_child_is_region_slot(root, slot) {
      continue
    }
    let child_id = hot_child_get(func, root_id, slot)
    if child_id == HOT_NONE_NODE || !hot_node_is_live(func, child_id) {
      continue
    }
    if hot_verify_node_tree_contains(func, child_id, needle_id) {
      return true
    }
  }
  false
}

///|
fn hot_verify_cfg_block_for_node_tree(
  func : HotFunc,
  cfg : HotCfg,
  node_id : NodeId,
) -> BlockId? {
  match cfg_block_for_node(cfg, node_id) {
    Some(block_id) => return Some(block_id)
    None => ()
  }
  for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
    for root_id in cfg_block_get(cfg, block_id).nodes {
      if hot_verify_node_tree_contains(func, root_id, node_id) {
        return Some(block_id)
      }
    }
  }
  None
}

///|
fn hot_verify_region_root_is_valid(
  func : HotFunc,
  entry : CfgRegionRootBlock,
) -> Bool {
  let region_ref = if entry.region_holder == HOT_NONE_NODE {
    HotRegionRef::root()
  } else {
    if !hot_node_is_live(func, entry.region_holder) {
      return false
    }
    match hot_node_get(func, entry.region_holder).op {
      HotOp::Block | HotOp::Loop => { holder: entry.region_holder, }
      _ => return false
    }
  }
  entry.slot >= 0 && entry.slot < hot_region_root_count(func, region_ref)
}

///|
pub fn hot_verify_cfg(
  func : HotFunc,
  cfg : HotCfg,
) -> Result[Unit, HotVerifyError] {
  match hot_verify_control(func) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if !hot_verify_block_id_is_valid(cfg, cfg_entry_block(cfg)) {
    return Err(HotVerifyError::InvalidCfgEntry(cfg_entry_block(cfg)))
  }
  if !hot_verify_block_id_is_valid(cfg, cfg_exit_block(cfg)) {
    return Err(HotVerifyError::InvalidCfgExit(cfg_exit_block(cfg)))
  }
  if !cfg_block_get(cfg, cfg_entry_block(cfg)).is_synthetic {
    return Err(HotVerifyError::InvalidCfgEntry(cfg_entry_block(cfg)))
  }
  if !cfg_block_get(cfg, cfg_exit_block(cfg)).is_synthetic {
    return Err(HotVerifyError::InvalidCfgExit(cfg_exit_block(cfg)))
  }
  match cfg_exceptional_exit_block(cfg) {
    Some(block_id) =>
      if !hot_verify_block_id_is_valid(cfg, block_id) ||
        !cfg_block_get(cfg, block_id).is_synthetic {
        return Err(HotVerifyError::InvalidCfgExit(block_id))
      }
    None => ()
  }
  if cfg.node_to_block.length() != hot_node_count(func) {
    return Err(
      HotVerifyError::InvalidCfgShape(
        "node_to_block",
        cfg.node_to_block.length(),
        hot_node_count(func),
      ),
    )
  }
  for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
    let block = cfg_block_get(cfg, block_id)
    if block.id != block_id {
      return Err(HotVerifyError::InvalidCfgBlockId(block_id, block.id))
    }
    for node_id in block.nodes {
      if !hot_node_is_live(func, node_id) {
        return Err(HotVerifyError::InvalidCfgBlockNode(block_id, node_id))
      }
      match cfg.node_to_block.get(node_id) {
        Some(mapped) if mapped == block_id => ()
        Some(mapped) =>
          return Err(HotVerifyError::InvalidCfgNodeMapping(node_id, mapped))
        None =>
          return Err(HotVerifyError::InvalidCfgBlockNode(block_id, node_id))
      }
    }
    for succ in block.succs {
      if !hot_verify_block_id_is_valid(cfg, succ.block) {
        return Err(HotVerifyError::InvalidCfgEdge(block_id, succ))
      }
      if !hot_verify_cfg_edge_has_symmetry(cfg, block_id, succ) {
        return Err(HotVerifyError::InvalidCfgEdgeSymmetry(block_id, succ))
      }
    }
    for pred in block.preds {
      if !hot_verify_block_id_is_valid(cfg, pred.block) {
        return Err(HotVerifyError::InvalidCfgEdge(block_id, pred))
      }
      if !hot_verify_cfg_pred_has_symmetry(cfg, block_id, pred) {
        return Err(HotVerifyError::InvalidCfgEdgeSymmetry(block_id, pred))
      }
    }
  }
  for node_id = 0; node_id < cfg.node_to_block.length(); node_id = node_id + 1 {
    let block_id = cfg.node_to_block[node_id]
    if block_id < 0 {
      continue
    }
    if !hot_node_is_live(func, node_id) ||
      !hot_verify_block_id_is_valid(cfg, block_id) ||
      !hot_verify_cfg_block_contains_node(cfg, block_id, node_id) {
      return Err(HotVerifyError::InvalidCfgNodeMapping(node_id, block_id))
    }
  }
  for entry in cfg.region_roots {
    if !hot_verify_block_id_is_valid(cfg, entry.block) ||
      !hot_verify_region_root_is_valid(func, entry) {
      return Err(
        HotVerifyError::InvalidCfgRegionRoot(
          entry.region_holder,
          entry.slot,
          entry.block,
        ),
      )
    }
    let block = cfg_block_get(cfg, entry.block)
    if block.region_holder != entry.region_holder ||
      entry.slot < block.start_slot ||
      entry.slot >= block.end_slot {
      return Err(
        HotVerifyError::InvalidCfgRegionRoot(
          entry.region_holder,
          entry.slot,
          entry.block,
        ),
      )
    }
  }
  Ok(())
}

///|
fn hot_verify_dom_reachable(dom : HotDominators, block_id : BlockId) -> Bool {
  if block_id < 0 || block_id >= dom.reachable.size {
    return false
  }
  bitset_contains(dom.reachable, block_id)
}

///|
pub fn hot_verify_dominators(
  cfg : HotCfg,
  dom : HotDominators,
) -> Result[Unit, HotVerifyError] {
  let block_count = cfg_block_count(cfg)
  if dom.entry_block != cfg_entry_block(cfg) {
    return Err(
      HotVerifyError::InvalidDomEntry(dom.entry_block, cfg_entry_block(cfg)),
    )
  }
  if dom.reachable.size != block_count {
    return Err(
      HotVerifyError::InvalidDomShape(
        "reachable",
        dom.reachable.size,
        block_count,
      ),
    )
  }
  if dom.idoms.length() != block_count {
    return Err(
      HotVerifyError::InvalidDomShape("idoms", dom.idoms.length(), block_count),
    )
  }
  if dom.children.length() != block_count {
    return Err(
      HotVerifyError::InvalidDomShape(
        "children",
        dom.children.length(),
        block_count,
      ),
    )
  }
  if dom.frontiers.length() != block_count {
    return Err(
      HotVerifyError::InvalidDomShape(
        "frontiers",
        dom.frontiers.length(),
        block_count,
      ),
    )
  }
  if dom.dom_preorder.length() != block_count {
    return Err(
      HotVerifyError::InvalidDomShape(
        "dom_preorder",
        dom.dom_preorder.length(),
        block_count,
      ),
    )
  }
  if dom.dom_postorder.length() != block_count {
    return Err(
      HotVerifyError::InvalidDomShape(
        "dom_postorder",
        dom.dom_postorder.length(),
        block_count,
      ),
    )
  }
  if !hot_verify_dom_reachable(dom, dom.entry_block) {
    return Err(HotVerifyError::InvalidDomReachable(dom.entry_block))
  }
  let child_counts = Array::make(block_count, 0)
  for block_id = 0; block_id < block_count; block_id = block_id + 1 {
    if !hot_verify_dom_reachable(dom, block_id) {
      if dom.idoms[block_id] != -1 {
        return Err(
          HotVerifyError::InvalidDomIdom(block_id, dom.idoms[block_id]),
        )
      }
      continue
    }
    if dom.dom_preorder[block_id] < 0 || dom.dom_postorder[block_id] < 0 {
      return Err(HotVerifyError::InvalidDomReachable(block_id))
    }
    if block_id == dom.entry_block {
      if dom.idoms[block_id] != block_id {
        return Err(
          HotVerifyError::InvalidDomIdom(block_id, dom.idoms[block_id]),
        )
      }
    } else {
      let idom = dom.idoms[block_id]
      if !hot_verify_block_id_is_valid(cfg, idom) ||
        !hot_verify_dom_reachable(dom, idom) {
        return Err(HotVerifyError::InvalidDomIdom(block_id, idom))
      }
      if !dom_dominates(dom, idom, block_id) {
        return Err(HotVerifyError::InvalidDomIdom(block_id, idom))
      }
    }
  }
  for parent = 0; parent < block_count; parent = parent + 1 {
    for child in dom.children[parent] {
      if !hot_verify_block_id_is_valid(cfg, child) || dom.idoms[child] != parent {
        return Err(HotVerifyError::InvalidDomChild(parent, child))
      }
      child_counts[child] = child_counts[child] + 1
    }
    for frontier in dom.frontiers[parent] {
      if !hot_verify_block_id_is_valid(cfg, frontier) ||
        !hot_verify_dom_reachable(dom, frontier) {
        return Err(HotVerifyError::InvalidDomFrontier(parent, frontier))
      }
    }
  }
  for block_id = 0; block_id < block_count; block_id = block_id + 1 {
    if !hot_verify_dom_reachable(dom, block_id) || block_id == dom.entry_block {
      continue
    }
    if child_counts[block_id] != 1 {
      return Err(HotVerifyError::InvalidDomChild(dom.idoms[block_id], block_id))
    }
  }
  Ok(())
}

///|
fn hot_verify_ssa_value_is_valid(
  ssa : HotLocalSsa,
  value_id : SsaValueId,
) -> Bool {
  value_id >= 0 && value_id < ssa.value_types.length()
}

///|
fn hot_verify_ssa_phi_is_valid(ssa : HotLocalSsa, phi_id : PhiId) -> Bool {
  phi_id >= 0 && phi_id < ssa.phis.length()
}

///|
fn hot_verify_ssa_value_def_block(
  func : HotFunc,
  cfg : HotCfg,
  ssa : HotLocalSsa,
  value_id : SsaValueId,
) -> BlockId? {
  match ssa.value_origins[value_id] {
    HotSsaValueOrigin::EntryParamDef(_)
    | HotSsaValueOrigin::EntryDefaultInitDef(_) => Some(cfg_entry_block(cfg))
    HotSsaValueOrigin::LocalSetDef(node_id)
    | HotSsaValueOrigin::LocalTeeDef(node_id) =>
      hot_verify_cfg_block_for_node_tree(func, cfg, node_id)
    HotSsaValueOrigin::PhiDef(phi_id) => Some(ssa.phis[phi_id].block)
  }
}

///|
fn hot_verify_ssa_normal_predecessors(
  cfg : HotCfg,
  dom : HotDominators,
  block_id : BlockId,
) -> Array[BlockId] {
  let preds : Array[BlockId] = []
  for pred in cfg_predecessor_edges(cfg, block_id) {
    if pred.kind == CfgEdgeKind::ExceptionalEdge ||
      !dom_dominates(dom, dom.entry_block, pred.block) {
      continue
    }
    preds.push(pred.block)
  }
  preds
}

///|
fn hot_verify_ssa_type_for_local(
  func : HotFunc,
  local_id : LocalId,
) -> Result[ValType, HotVerifyError] {
  match hot_local_type(func, local_id) {
    Some(ty) => Ok(ty)
    None => Err(HotVerifyError::InvalidSsaLocal(local_id))
  }
}

///|
fn hot_verify_ssa_use_value(
  func : HotFunc,
  cfg : HotCfg,
  dom : HotDominators,
  ssa : HotLocalSsa,
  value_id : SsaValueId,
  use_block : BlockId,
) -> Result[Unit, HotVerifyError] {
  if !hot_verify_ssa_value_is_valid(ssa, value_id) {
    return Err(HotVerifyError::InvalidSsaValue(value_id))
  }
  match hot_verify_ssa_value_def_block(func, cfg, ssa, value_id) {
    Some(def_block) =>
      if !dom_dominates(dom, def_block, use_block) {
        return Err(
          HotVerifyError::InvalidSsaDominance(value_id, def_block, use_block),
        )
      }
    None => return Err(HotVerifyError::InvalidSsaValue(value_id))
  }
  Ok(())
}

///|
pub fn hot_verify_ssa(
  func : HotFunc,
  cfg : HotCfg,
  ssa : HotLocalSsa,
) -> Result[Unit, HotVerifyError] {
  match hot_verify_cfg(func, cfg) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let dom = dom_build(cfg)
  match hot_verify_dominators(cfg, dom) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if ssa.revision != hot_revision_current(func) {
    return Err(
      HotVerifyError::InvalidSsaRevision(
        ssa.revision,
        hot_revision_current(func),
      ),
    )
  }
  let node_count = hot_node_count(func)
  let local_count = hot_local_count(func)
  let block_count = cfg_block_count(cfg)
  if ssa.local_get_values.length() != node_count {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "local_get_values",
        ssa.local_get_values.length(),
        node_count,
      ),
    )
  }
  if ssa.local_write_defs.length() != node_count {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "local_write_defs",
        ssa.local_write_defs.length(),
        node_count,
      ),
    )
  }
  if ssa.block_phis.length() != block_count {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "block_phis",
        ssa.block_phis.length(),
        block_count,
      ),
    )
  }
  if ssa.entry_defs.length() != local_count {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "entry_defs",
        ssa.entry_defs.length(),
        local_count,
      ),
    )
  }
  if ssa.value_origins.length() != ssa.value_types.length() {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "value_origins",
        ssa.value_origins.length(),
        ssa.value_types.length(),
      ),
    )
  }
  if ssa.value_uses.length() != ssa.value_types.length() {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "value_uses",
        ssa.value_uses.length(),
        ssa.value_types.length(),
      ),
    )
  }
  if ssa.phi_value_ids.length() != ssa.phis.length() {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "phi_value_ids",
        ssa.phi_value_ids.length(),
        ssa.phis.length(),
      ),
    )
  }
  if ssa.phi_inputs.length() != ssa.phis.length() {
    return Err(
      HotVerifyError::InvalidSsaShape(
        "phi_inputs",
        ssa.phi_inputs.length(),
        ssa.phis.length(),
      ),
    )
  }

  for local_id = 0; local_id < local_count; local_id = local_id + 1 {
    let value_id = ssa.entry_defs[local_id]
    if !hot_verify_ssa_value_is_valid(ssa, value_id) {
      return Err(HotVerifyError::InvalidSsaValue(value_id))
    }
    let expected = match hot_verify_ssa_type_for_local(func, local_id) {
      Ok(ty) => ty
      Err(err) => return Err(err)
    }
    if ssa.value_types[value_id] != expected {
      return Err(
        HotVerifyError::InvalidSsaType(
          value_id,
          ssa.value_types[value_id],
          expected,
        ),
      )
    }
  }

  for value_id = 0; value_id < ssa.value_types.length(); value_id = value_id + 1 {
    match ssa.value_origins[value_id] {
      HotSsaValueOrigin::EntryParamDef(local_id)
      | HotSsaValueOrigin::EntryDefaultInitDef(local_id) => {
        if local_id < 0 ||
          local_id >= local_count ||
          ssa.entry_defs[local_id] != value_id {
          return Err(
            HotVerifyError::InvalidSsaValueOrigin(
              value_id,
              ssa.value_origins[value_id],
            ),
          )
        }
        let expected = match hot_verify_ssa_type_for_local(func, local_id) {
          Ok(ty) => ty
          Err(err) => return Err(err)
        }
        if ssa.value_types[value_id] != expected {
          return Err(
            HotVerifyError::InvalidSsaType(
              value_id,
              ssa.value_types[value_id],
              expected,
            ),
          )
        }
      }
      HotSsaValueOrigin::LocalSetDef(node_id)
      | HotSsaValueOrigin::LocalTeeDef(node_id) => {
        if node_id < 0 ||
          node_id >= node_count ||
          !hot_node_is_live(func, node_id) {
          return Err(
            HotVerifyError::InvalidSsaValueOrigin(
              value_id,
              ssa.value_origins[value_id],
            ),
          )
        }
        let node = hot_node_get(func, node_id)
        let is_set_origin = match ssa.value_origins[value_id] {
          HotSsaValueOrigin::LocalSetDef(_) => node.op == HotOp::LocalSet
          HotSsaValueOrigin::LocalTeeDef(_) => node.op == HotOp::LocalTee
          _ => false
        }
        if !is_set_origin || ssa.local_write_defs[node_id] != value_id {
          return Err(
            HotVerifyError::InvalidSsaValueOrigin(
              value_id,
              ssa.value_origins[value_id],
            ),
          )
        }
        let expected = match hot_verify_ssa_type_for_local(func, node.imm0) {
          Ok(ty) => ty
          Err(err) => return Err(err)
        }
        if ssa.value_types[value_id] != expected {
          return Err(
            HotVerifyError::InvalidSsaType(
              value_id,
              ssa.value_types[value_id],
              expected,
            ),
          )
        }
      }
      HotSsaValueOrigin::PhiDef(phi_id) =>
        if !hot_verify_ssa_phi_is_valid(ssa, phi_id) ||
          ssa.phi_value_ids[phi_id] != value_id {
          return Err(
            HotVerifyError::InvalidSsaValueOrigin(
              value_id,
              ssa.value_origins[value_id],
            ),
          )
        }
    }
  }

  for block_id = 0; block_id < block_count; block_id = block_id + 1 {
    let seen_locals = Array::make(local_count, false)
    for phi_id in ssa.block_phis[block_id] {
      if !hot_verify_ssa_phi_is_valid(ssa, phi_id) {
        return Err(HotVerifyError::InvalidSsaPhi(phi_id))
      }
      let phi = ssa.phis[phi_id]
      if phi.block != block_id ||
        phi.local_id < 0 ||
        phi.local_id >= local_count {
        return Err(HotVerifyError::InvalidSsaPhi(phi_id))
      }
      if seen_locals[phi.local_id] {
        return Err(HotVerifyError::InvalidSsaPhi(phi_id))
      }
      seen_locals[phi.local_id] = true
      let value_id = ssa.phi_value_ids[phi_id]
      if !hot_verify_ssa_value_is_valid(ssa, value_id) {
        return Err(HotVerifyError::InvalidSsaValue(value_id))
      }
      let expected = match hot_verify_ssa_type_for_local(func, phi.local_id) {
        Ok(ty) => ty
        Err(err) => return Err(err)
      }
      if ssa.value_types[value_id] != expected {
        return Err(
          HotVerifyError::InvalidSsaType(
            value_id,
            ssa.value_types[value_id],
            expected,
          ),
        )
      }
      let preds = hot_verify_ssa_normal_predecessors(cfg, dom, block_id)
      if ssa.phi_inputs[phi_id].length() != preds.length() {
        return Err(
          HotVerifyError::InvalidSsaPhiAlignment(
            phi_id,
            block_id,
            ssa.phi_inputs[phi_id].length(),
            preds.length(),
          ),
        )
      }
      let seen_preds = Array::make(block_count, false)
      for input in ssa.phi_inputs[phi_id] {
        let mut found_pred = false
        for pred in preds {
          if pred == input.predecessor {
            found_pred = true
            break
          }
        }
        if !found_pred ||
          seen_preds[input.predecessor] ||
          !hot_verify_ssa_value_is_valid(ssa, input.value_id) {
          return Err(
            HotVerifyError::InvalidSsaPhiInput(
              phi_id,
              input.predecessor,
              input.value_id,
            ),
          )
        }
        seen_preds[input.predecessor] = true
        if ssa.value_types[input.value_id] != expected {
          return Err(
            HotVerifyError::InvalidSsaType(
              input.value_id,
              ssa.value_types[input.value_id],
              expected,
            ),
          )
        }
        let mut listed_use = false
        for use_origin in ssa.value_uses[input.value_id] {
          match use_origin {
            HotSsaUseOrigin::PhiInputUse(listed_phi, listed_pred) =>
              if listed_phi == phi_id && listed_pred == input.predecessor {
                listed_use = true
                break
              }
            _ => ()
          }
        }
        if !listed_use {
          return Err(
            HotVerifyError::InvalidSsaUse(
              input.value_id,
              HotSsaUseOrigin::PhiInputUse(phi_id, input.predecessor),
            ),
          )
        }
        match
          hot_verify_ssa_use_value(
            func,
            cfg,
            dom,
            ssa,
            input.value_id,
            input.predecessor,
          ) {
          Ok(_) => ()
          Err(err) => return Err(err)
        }
      }
    }
  }

  for phi_id = 0; phi_id < ssa.phis.length(); phi_id = phi_id + 1 {
    let phi = ssa.phis[phi_id]
    if !hot_verify_block_id_is_valid(cfg, phi.block) ||
      phi.local_id < 0 ||
      phi.local_id >= local_count {
      return Err(HotVerifyError::InvalidSsaPhi(phi_id))
    }
    let mut found = false
    for mapped in ssa.block_phis[phi.block] {
      if mapped == phi_id {
        found = true
        break
      }
    }
    if !found {
      return Err(HotVerifyError::InvalidSsaPhi(phi_id))
    }
  }

  for node_id = 0; node_id < node_count; node_id = node_id + 1 {
    if !hot_node_is_live(func, node_id) {
      continue
    }
    let node = hot_node_get(func, node_id)
    match node.op {
      HotOp::LocalGet => {
        let value_id = ssa.local_get_values[node_id]
        if !hot_verify_ssa_value_is_valid(ssa, value_id) {
          if value_id == -1 {
            match hot_verify_cfg_block_for_node_tree(func, cfg, node_id) {
              Some(block_id) if !hot_verify_dom_reachable(dom, block_id) =>
                continue
              _ => ()
            }
          }
          return Err(HotVerifyError::InvalidSsaValue(value_id))
        }
        let expected = match hot_verify_ssa_type_for_local(func, node.imm0) {
          Ok(ty) => ty
          Err(err) => return Err(err)
        }
        if ssa.value_types[value_id] != expected {
          return Err(
            HotVerifyError::InvalidSsaType(
              value_id,
              ssa.value_types[value_id],
              expected,
            ),
          )
        }
        let mut listed_use = false
        for use_origin in ssa.value_uses[value_id] {
          match use_origin {
            HotSsaUseOrigin::LocalGetUse(listed_node) =>
              if listed_node == node_id {
                listed_use = true
                break
              }
            _ => ()
          }
        }
        if !listed_use {
          return Err(
            HotVerifyError::InvalidSsaUse(
              value_id,
              HotSsaUseOrigin::LocalGetUse(node_id),
            ),
          )
        }
        match hot_verify_cfg_block_for_node_tree(func, cfg, node_id) {
          Some(use_block) =>
            match
              hot_verify_ssa_use_value(func, cfg, dom, ssa, value_id, use_block) {
              Ok(_) => ()
              Err(err) => return Err(err)
            }
          None => return Err(HotVerifyError::InvalidSsaNode(node_id))
        }
      }
      HotOp::LocalSet | HotOp::LocalTee => {
        let value_id = ssa.local_write_defs[node_id]
        if !hot_verify_ssa_value_is_valid(ssa, value_id) {
          if value_id == -1 {
            match hot_verify_cfg_block_for_node_tree(func, cfg, node_id) {
              Some(block_id) if !hot_verify_dom_reachable(dom, block_id) =>
                continue
              _ => ()
            }
          }
          return Err(HotVerifyError::InvalidSsaValue(value_id))
        }
      }
      _ => ()
    }
  }

  for value_id = 0; value_id < ssa.value_uses.length(); value_id = value_id + 1 {
    for use_origin in ssa.value_uses[value_id] {
      match use_origin {
        HotSsaUseOrigin::LocalGetUse(node_id) =>
          if node_id < 0 ||
            node_id >= node_count ||
            ssa.local_get_values[node_id] != value_id {
            return Err(HotVerifyError::InvalidSsaUse(value_id, use_origin))
          }
        HotSsaUseOrigin::PhiInputUse(phi_id, predecessor) => {
          if !hot_verify_ssa_phi_is_valid(ssa, phi_id) {
            return Err(HotVerifyError::InvalidSsaUse(value_id, use_origin))
          }
          let mut found = false
          for input in ssa.phi_inputs[phi_id] {
            if input.predecessor == predecessor && input.value_id == value_id {
              found = true
              break
            }
          }
          if !found {
            return Err(HotVerifyError::InvalidSsaUse(value_id, use_origin))
          }
        }
      }
    }
  }
  Ok(())
}

///|
pub fn hot_verify_cached_analyses(
  func : HotFunc,
  cache : HotAnalysisCache,
) -> Result[Unit, HotVerifyError] {
  match cache.owner_roots {
    Some(roots) if !physical_equal(roots, func.roots) =>
      return Err(HotVerifyError::InvalidAnalysisOwner)
    _ => ()
  }
  let revision = hot_revision_current(func)
  match cache.cfg {
    Some(entry) => {
      if entry.built_at_revision != revision {
        return Err(
          HotVerifyError::InvalidAnalysisRevision(
            HotAnalysis::Cfg,
            entry.built_at_revision,
            revision,
          ),
        )
      }
      match hot_verify_cfg(func, entry.value) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
    None => ()
  }
  match cache.orders {
    Some(entry) if entry.built_at_revision != revision =>
      return Err(
        HotVerifyError::InvalidAnalysisRevision(
          HotAnalysis::Cfg,
          entry.built_at_revision,
          revision,
        ),
      )
    _ => ()
  }
  match cache.dom {
    Some(entry) => {
      if entry.built_at_revision != revision {
        return Err(
          HotVerifyError::InvalidAnalysisRevision(
            HotAnalysis::Dominance,
            entry.built_at_revision,
            revision,
          ),
        )
      }
      let cfg = match cache.cfg {
        Some(cfg_entry) if cfg_entry.built_at_revision == revision =>
          cfg_entry.value
        _ =>
          return Err(
            HotVerifyError::InvalidAnalysisDependency(
              HotAnalysis::Dominance,
              HotAnalysis::Cfg,
            ),
          )
      }
      match hot_verify_dominators(cfg, entry.value) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
    None => ()
  }
  match cache.postdom {
    Some(entry) if entry.built_at_revision != revision =>
      return Err(
        HotVerifyError::InvalidAnalysisRevision(
          HotAnalysis::Dominance,
          entry.built_at_revision,
          revision,
        ),
      )
    _ => ()
  }
  match cache.loop_info {
    Some(entry) if entry.built_at_revision != revision =>
      return Err(
        HotVerifyError::InvalidAnalysisRevision(
          HotAnalysis::LoopInfo,
          entry.built_at_revision,
          revision,
        ),
      )
    _ => ()
  }
  match cache.use_def {
    Some(entry) if entry.built_at_revision != revision =>
      return Err(
        HotVerifyError::InvalidAnalysisRevision(
          HotAnalysis::UseDef,
          entry.built_at_revision,
          revision,
        ),
      )
    _ => ()
  }
  match cache.liveness {
    Some(entry) if entry.built_at_revision != revision =>
      return Err(
        HotVerifyError::InvalidAnalysisRevision(
          HotAnalysis::Liveness,
          entry.built_at_revision,
          revision,
        ),
      )
    _ => ()
  }
  match cache.effects {
    Some(entry) if entry.built_at_revision != revision =>
      return Err(
        HotVerifyError::InvalidAnalysisRevision(
          HotAnalysis::Effects,
          entry.built_at_revision,
          revision,
        ),
      )
    _ => ()
  }
  match cache.ssa {
    Some(entry) => {
      if entry.built_at_revision != revision {
        return Err(
          HotVerifyError::InvalidAnalysisRevision(
            HotAnalysis::Ssa,
            entry.built_at_revision,
            revision,
          ),
        )
      }
      let cfg = match cache.cfg {
        Some(cfg_entry) if cfg_entry.built_at_revision == revision =>
          cfg_entry.value
        _ =>
          return Err(
            HotVerifyError::InvalidAnalysisDependency(
              HotAnalysis::Ssa,
              HotAnalysis::Cfg,
            ),
          )
      }
      match hot_verify_ssa(func, cfg, entry.value) {
        Ok(_) => ()
        Err(err) => return Err(err)
      }
    }
    None => ()
  }
  Ok(())
}

///|
pub fn[C] hot_verify_all(
  func : HotFunc,
  cache~ : C? = None,
) -> Result[Unit, HotVerifyError] {
  let _ = cache
  hot_verify_all_impl(func)
}

///|
fn hot_verify_all_impl(
  func : HotFunc,
  core_verification_calls~ : Array[Int32]? = None,
) -> Result[Unit, HotVerifyError] {
  // Control verification begins with the complete core verifier. No mutation
  // separates the two original calls, so repeating core adds no checks and
  // does not change which error is returned first.
  match core_verification_calls {
    // An explicitly forwarded None would box Some(None) at this optional-
    // argument boundary. Keep default verification on the uncounted entry.
    None => hot_verify_control(func)
    Some(calls) =>
      hot_verify_control_impl(func, core_verification_calls=Some(calls))
  }
}

///|
pub fn hot_verify_core_or_abort(func : HotFunc) -> Unit {
  match hot_verify_core(func) {
    Ok(_) => ()
    Err(err) => abort(err.to_string())
  }
}

///|
pub fn hot_verify_control_or_abort(func : HotFunc) -> Unit {
  match hot_verify_control(func) {
    Ok(_) => ()
    Err(err) => abort(err.to_string())
  }
}

///|
pub fn hot_verify_all_or_abort(func : HotFunc) -> Unit {
  match hot_verify_all(func, cache=Some(())) {
    Ok(_) => ()
    Err(err) => abort(err.to_string())
  }
}