///|
pub(all) enum RegClass {
  Int
  Float
  Vector
} derive(Eq, Debug)

///|
pub(all) struct VirtualReg {
  id : Int
  class : RegClass
} derive(Eq, Debug)

///|
pub(all) struct PhysicalReg {
  id : Int
  class : RegClass
} derive(Eq, Debug)

///|
fn physical_reg_key(reg : PhysicalReg) -> (Int, Int) {
  let class = match reg.class {
    Int => 0
    Float => 1
    Vector => 2
  }
  (class, reg.id)
}

///|
pub(all) enum Location {
  Reg(PhysicalReg)
  Spill(Int)
} derive(Eq, Debug)

///|
pub(all) enum OperandRole {
  Use
  Def
  UseDef
} derive(Eq, Debug)

///|
pub(all) enum OperandTiming {
  Early
  Late
} derive(Eq, Debug)

///|
pub(all) struct Operand {
  vreg : VirtualReg
  role : OperandRole
  constraint : OperandConstraint
  preference : PhysicalReg?
  tie_id : Int
  timing : OperandTiming
} derive(Eq, Debug)

///|
pub fn Operand::use_reg(vreg : VirtualReg) -> Operand {
  {
    vreg,
    role: Use,
    constraint: AnyReg,
    preference: None,
    tie_id: -1,
    timing: Early,
  }
}

///|
pub fn Operand::def(vreg : VirtualReg) -> Operand {
  {
    vreg,
    role: Def,
    constraint: AnyReg,
    preference: None,
    tie_id: -1,
    timing: Late,
  }
}

///|
pub fn Operand::with_constraint(
  self : Operand,
  constraint : OperandConstraint,
) -> Operand {
  {
    vreg: self.vreg,
    role: self.role,
    constraint,
    preference: self.preference,
    tie_id: self.tie_id,
    timing: self.timing,
  }
}

///|
/// Prefer a physical register without making it a correctness constraint.
/// Allocation may ignore the preference when that register is unavailable.
pub fn Operand::with_preference(
  self : Operand,
  preference : PhysicalReg,
) -> Operand {
  { ..self, preference: Some(preference) }
}

///|
/// Tie operands in one instruction by a shared nonnegative label.
/// Labels are opaque and need not be contiguous or bounded by operand count.
pub fn Operand::with_tie(self : Operand, tie_id : Int) -> Operand {
  {
    vreg: self.vreg,
    role: self.role,
    constraint: self.constraint,
    preference: self.preference,
    tie_id,
    timing: self.timing,
  }
}

///|
pub fn Operand::with_timing(self : Operand, timing : OperandTiming) -> Operand {
  {
    vreg: self.vreg,
    role: self.role,
    constraint: self.constraint,
    preference: self.preference,
    tie_id: self.tie_id,
    timing,
  }
}