///|
pub enum HotSideTableKind {
  ConstPayloadTable
  MemArgTable
  BranchTableTable
  CatchInfoTable
  CallSigTable
  InstrPayloadTable
} derive(Eq, Debug)

///|
pub fn HotConstPayload::i32_const(value : I32) -> HotConstPayload {
  I32Const(value)
}

///|
pub fn HotConstPayload::i64_const(value : I64) -> HotConstPayload {
  I64Const(value)
}

///|
pub fn HotConstPayload::f32_const(value : F32) -> HotConstPayload {
  F32Const(value)
}

///|
pub fn HotConstPayload::f64_const(value : F64) -> HotConstPayload {
  F64Const(value)
}

///|
pub fn HotConstPayload::ref_null_const(value : RefType) -> HotConstPayload {
  RefNullConst(value)
}

///|
pub fn HotConstPayload::string_const(value : Bytes) -> HotConstPayload {
  StringConst(value)
}

///|
pub fn HotMemArg::new(memarg : MemArg) -> HotMemArg {
  { memarg, }
}

///|
pub fn HotBranchTable::new(
  targets : Array[LabelId],
  default_target : LabelId,
) -> HotBranchTable {
  { targets: targets.copy(), default_target, }
}

///|
pub fn HotCatchArm::catch_(tag_idx : TagIdx, label_id : LabelId) -> HotCatchArm {
  CatchArm(tag_idx, label_id)
}

///|
pub fn HotCatchArm::catch_ref(
  tag_idx : TagIdx,
  label_id : LabelId,
) -> HotCatchArm {
  CatchRefArm(tag_idx, label_id)
}

///|
pub fn HotCatchArm::all(label_id : LabelId) -> HotCatchArm {
  CatchAllArm(label_id)
}

///|
pub fn HotCatchArm::all_ref(label_id : LabelId) -> HotCatchArm {
  CatchAllRefArm(label_id)
}

///|
pub fn HotCatchInfo::new(catches : Array[HotCatchArm]) -> HotCatchInfo {
  { catches: catches.copy(), }
}

///|
pub fn HotCallSig::new(
  type_idx : TypeIdx,
  table_idx~ : TableIdx? = None,
) -> HotCallSig {
  { type_idx, table_idx, }
}

///|
pub fn HotInstrPayload::new(instr : Instruction) -> HotInstrPayload {
  { instr, }
}

///|
pub fn hot_alloc_const(
  func : HotFunc,
  payload : HotConstPayload,
) -> SideTableId {
  let id = func.consts.length()
  func.consts.push(payload)
  id
}

///|
pub fn hot_const_get(func : HotFunc, id : SideTableId) -> HotConstPayload {
  func.consts[id]
}

///|
pub fn hot_alloc_memarg(func : HotFunc, memarg : HotMemArg) -> SideTableId {
  let id = func.memargs.length()
  func.memargs.push(memarg)
  id
}

///|
pub fn hot_memarg_get(func : HotFunc, id : SideTableId) -> HotMemArg {
  func.memargs[id]
}

///|
pub fn hot_alloc_branch_table(
  func : HotFunc,
  table : HotBranchTable,
) -> SideTableId {
  let id = func.branch_tables.length()
  func.branch_tables.push(
    HotBranchTable::new(table.targets, table.default_target),
  )
  id
}

///|
pub fn hot_branch_table_targets(
  func : HotFunc,
  id : SideTableId,
) -> Array[LabelId] {
  func.branch_tables[id].targets.copy()
}

///|
pub fn hot_branch_table_default_target(
  func : HotFunc,
  id : SideTableId,
) -> LabelId {
  func.branch_tables[id].default_target
}

///|
pub fn hot_alloc_catch_info(func : HotFunc, info : HotCatchInfo) -> SideTableId {
  let id = func.catches.length()
  func.catches.push(HotCatchInfo::new(info.catches))
  id
}

///|
pub fn hot_catch_info_get(func : HotFunc, id : SideTableId) -> HotCatchInfo {
  { catches: func.catches[id].catches.copy(), }
}

///|
pub fn hot_alloc_call_sig(func : HotFunc, sig : HotCallSig) -> SideTableId {
  let id = func.call_sigs.length()
  func.call_sigs.push(sig)
  id
}

///|
pub fn hot_call_sig_get(func : HotFunc, id : SideTableId) -> HotCallSig {
  func.call_sigs[id]
}

///|
pub fn hot_alloc_instr_payload(
  func : HotFunc,
  payload : HotInstrPayload,
) -> SideTableId {
  let id = func.instr_payloads.length()
  func.instr_payloads.push(payload)
  id
}

///|
pub fn hot_instr_payload_get(
  func : HotFunc,
  id : SideTableId,
) -> HotInstrPayload {
  func.instr_payloads[id]
}

///|
pub fn hot_node_exact_instr_payload_id(
  func : HotFunc,
  node_id : NodeId,
) -> SideTableId? {
  // Retain the checked live-node admission, including incomplete-index fallback.
  // Select directly from scalar fields without another wrapper/header boundary.
  hot_node_require_live_read(func, node_id)
  let node = func.nodes[node_id]
  match node.op {
    HotOp::Load
    | HotOp::Store
    | HotOp::RefTest
    | HotOp::RefCast
    | HotOp::RefTestDesc
    | HotOp::RefCastDescEq
    | HotOp::BrOnCast
    | HotOp::BrOnCastFail => Some(node.imm1)
    HotOp::Select
    | HotOp::Heap
    | HotOp::Continuation
    | HotOp::Unary
    | HotOp::Binary
    | HotOp::Compare
    | HotOp::Convert
    | HotOp::StringOp
    | HotOp::Simd => Some(node.imm0)
    HotOp::Atomic => Some(node.imm1)
    _ => None
  }
}

///|
pub fn hot_node_exact_instr(func : HotFunc, node_id : NodeId) -> Instruction? {
  match hot_node_exact_instr_payload_id(func, node_id) {
    Some(payload_id) => Some(hot_instr_payload_get(func, payload_id).instr)
    None => None
  }
}

///|
pub fn hot_verify_node_exact_instr_payload(
  func : HotFunc,
  node_id : NodeId,
) -> Bool {
  match hot_node_exact_instr_payload_id(func, node_id) {
    Some(payload_id) =>
      payload_id >= 0 && payload_id < func.instr_payloads.length()
    None => true
  }
}

///|
pub fn hot_side_table_kind_for_op(op : HotOp) -> HotSideTableKind? {
  match op {
    HotOp::Const | HotOp::RefNull => Some(HotSideTableKind::ConstPayloadTable)
    HotOp::Load | HotOp::Store | HotOp::Atomic =>
      Some(HotSideTableKind::MemArgTable)
    HotOp::BrTable | HotOp::Continuation =>
      Some(HotSideTableKind::BranchTableTable)
    HotOp::TryTable => Some(HotSideTableKind::CatchInfoTable)
    HotOp::CallIndirect
    | HotOp::CallRef
    | HotOp::ReturnCallIndirect
    | HotOp::ReturnCallRef => Some(HotSideTableKind::CallSigTable)
    _ => None
  }
}