///|
pub(all) enum RegClass {
  Int
  FpVector
} derive(Eq, Debug, Hash)

///|
fn RegClass::text(self : RegClass) -> String {
  match self {
    Int => "int"
    FpVector => "fpvec"
  }
}

///|
pub type ValueType = @semantic.ValueType

///|
pub fn reg_class_for_value_type(ty : ValueType) -> RegClass {
  match ty {
    I32 | I64 | Ptr64 | GcRef64 => Int
    F32 | F64 | V128 => FpVector
  }
}

///|
fn value_type_text(ty : ValueType) -> String {
  match ty {
    I32 => "i32"
    I64 => "i64"
    F32 => "f32"
    F64 => "f64"
    V128 => "v128"
    Ptr64 => "ptr64"
    GcRef64 => "gcref64"
  }
}

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

///|
pub fn PhysicalReg::new(id : Int, class : RegClass) -> PhysicalReg {
  { id, class }
}

///|
pub impl Debug for PhysicalReg with fn to_repr(self) {
  Repr::literal("p\{self.id}:\{self.class.text()}")
}

///|
pub struct Value {
  priv owner : Ref[Unit]
  priv id : Int
}

///|
fn Value::new(owner : Ref[Unit], id : Int) -> Value {
  { owner, id }
}

///|
pub impl Eq for Value with fn equal(self, other) {
  physical_equal(self.owner, other.owner) && self.id == other.id
}

///|
pub impl Debug for Value with fn to_repr(self) {
  Repr::literal("v\{self.id}")
}

///|
pub impl Show for Value with fn output(self, logger) {
  logger.write_string(Repr(self).to_string())
}

///|
pub struct Block {
  priv owner : Ref[Unit]
  priv id : Int
}

///|
fn Block::new(owner : Ref[Unit], id : Int) -> Block {
  { owner, id }
}

///|
pub impl Eq for Block with fn equal(self, other) {
  physical_equal(self.owner, other.owner) && self.id == other.id
}

///|
pub impl Debug for Block with fn to_repr(self) {
  Repr::literal("block\{self.id}")
}

///|
pub impl Show for Block with fn output(self, logger) {
  logger.write_string(Repr(self).to_string())
}

///|
pub struct Instruction {
  priv owner : Ref[Unit]
  priv id : Int
}

///|
fn Instruction::new(owner : Ref[Unit], id : Int) -> Instruction {
  { owner, id }
}

///|
pub impl Eq for Instruction with fn equal(self, other) {
  physical_equal(self.owner, other.owner) && self.id == other.id
}

///|
pub impl Debug for Instruction with fn to_repr(self) {
  Repr::literal("i\{self.id}")
}

///|
pub impl Show for Instruction with fn output(self, logger) {
  logger.write_string(Repr(self).to_string())
}

///|
pub(all) enum PointPlacement {
  Before
  After
} derive(Eq, Debug)

///|
pub struct ProgramPoint {
  priv owner : Ref[Unit]
  priv instruction : Instruction
  priv placement : PointPlacement
}

///|
fn ProgramPoint::new(
  owner : Ref[Unit],
  instruction : Instruction,
  placement : PointPlacement,
) -> ProgramPoint {
  { owner, instruction, placement }
}

///|
pub impl Eq for ProgramPoint with fn equal(self, other) {
  physical_equal(self.owner, other.owner) &&
  self.instruction == other.instruction &&
  self.placement == other.placement
}

///|
pub impl Debug for ProgramPoint with fn to_repr(self) {
  let placement = match self.placement {
    Before => "before"
    After => "after"
  }
  Repr::literal("\{placement}(\{Repr(self.instruction)})")
}

///|
pub fn ProgramPoint::instruction(self : ProgramPoint) -> Instruction {
  self.instruction
}

///|
pub fn ProgramPoint::placement(self : ProgramPoint) -> PointPlacement {
  self.placement
}

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

///|
pub(all) enum OperandConstraint {
  Any
  AnyLocation
  Fixed(PhysicalReg)
  TiedTo(Int)
} derive(Eq, Debug)

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

///|
pub struct Input {
  value : Value
  constraint : OperandConstraint
  preference : PhysicalReg?
  timing : OperandTiming
}

///|
pub fn Input::any(value : Value) -> Input {
  { value, constraint: Any, preference: None, timing: Early }
}

///|
/// Keep an input in its allocated register or spill slot. This is intended for
/// target operations, such as ABI argument setup, whose emitter can consume a
/// stack-resident value directly instead of requiring every input in a register
/// at the same program point.
pub fn Input::any_location(value : Value) -> Input {
  { value, constraint: AnyLocation, preference: None, timing: Early }
}

///|
pub fn Input::fixed(value : Value, reg : PhysicalReg) -> Input {
  { value, constraint: Fixed(reg), preference: None, timing: Early }
}

///|
/// Prefer a physical register without making it an allocation constraint.
pub fn Input::with_preference(self : Input, preference : PhysicalReg) -> Input {
  { ..self, preference: Some(preference) }
}

///|
pub fn Input::with_timing(self : Input, timing : OperandTiming) -> Input {
  { ..self, timing, }
}

///|
pub struct Output {
  ty : ValueType
  constraint : OperandConstraint
  preference : PhysicalReg?
  timing : OperandTiming
}

///|
pub fn Output::any(ty : ValueType) -> Output {
  { ty, constraint: Any, preference: None, timing: Late }
}

///|
/// Materialize the result directly in its stable register or stack home.
/// This is intended for target ABI pseudos whose emitter owns the transfer
/// from an implicit incoming location.
pub fn Output::any_location(ty : ValueType) -> Output {
  { ty, constraint: AnyLocation, preference: None, timing: Late }
}

///|
pub fn Output::fixed(ty : ValueType, reg : PhysicalReg) -> Output {
  { ty, constraint: Fixed(reg), preference: None, timing: Late }
}

///|
pub fn Output::tied(ty : ValueType, operand_index : Int) -> Output {
  { ty, constraint: TiedTo(operand_index), preference: None, timing: Late }
}

///|
/// Prefer a physical register without making it an allocation constraint.
pub fn Output::with_preference(
  self : Output,
  preference : PhysicalReg,
) -> Output {
  { ..self, preference: Some(preference) }
}

///|
pub fn Output::with_timing(self : Output, timing : OperandTiming) -> Output {
  { ..self, timing, }
}

///|
priv struct OperandData {
  value : Value
  role : OperandRole
  constraint : OperandConstraint
  preference : PhysicalReg?
  timing : OperandTiming
  mut tie_id : Int
}

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

///|
fn OperandData::snapshot(self : OperandData) -> Operand {
  {
    value: self.value,
    role: self.role,
    constraint: self.constraint,
    preference: self.preference,
    timing: self.timing,
    tie_id: self.tie_id,
  }
}

///|
pub struct Edge {
  target : Block
  arguments : Array[Value]
}

///|
pub fn Edge::new(target : Block, arguments : Array[Value]) -> Edge {
  { target, arguments: arguments.copy() }
}

///|
fn Edge::copy(self : Edge) -> Edge {
  Edge::new(self.target, self.arguments)
}

///|
pub type SafepointKind = @semantic.SafepointKind

///|
pub(all) enum SymbolReference {
  Code(@semantic.CodeSymbol)
  External(@semantic.ExternalSymbol)
  Data(@semantic.DataSymbol)
} derive(Eq, Debug)

///|
pub struct InstructionMetadata {
  source : @semantic.SourceLocation?
  trap : @semantic.TrapReason?
  safepoint : SafepointKind?
  live_gc_roots : Array[Value]
  symbol : SymbolReference?
  stack_map : @semantic.StackMapMetadata?
}

///|
pub fn InstructionMetadata::empty() -> InstructionMetadata {
  {
    source: None,
    trap: None,
    safepoint: None,
    live_gc_roots: [],
    symbol: None,
    stack_map: None,
  }
}

///|
pub fn InstructionMetadata::safepoint(
  live_gc_roots : Array[Value],
) -> InstructionMetadata {
  {
    source: None,
    trap: None,
    safepoint: Some(Gc),
    live_gc_roots: live_gc_roots.copy(),
    symbol: None,
    stack_map: None,
  }
}

///|
pub fn InstructionMetadata::new(
  source? : @semantic.SourceLocation,
  trap? : @semantic.TrapReason,
  safepoint? : SafepointKind,
  live_gc_roots? : Array[Value] = [],
  symbol? : SymbolReference,
  stack_map? : @semantic.StackMapMetadata,
) -> InstructionMetadata {
  {
    source,
    trap,
    safepoint,
    live_gc_roots: live_gc_roots.copy(),
    symbol,
    stack_map,
  }
}

///|
fn InstructionMetadata::copy(self : InstructionMetadata) -> InstructionMetadata {
  {
    source: self.source,
    trap: self.trap,
    safepoint: self.safepoint,
    live_gc_roots: self.live_gc_roots.copy(),
    symbol: self.symbol,
    stack_map: self.stack_map,
  }
}

///|
pub suberror VCodeBuildError {
  InvalidParameter(index~ : Int)
  InvalidBlockParameter(block~ : Block, index~ : Int)
  ForeignBlock(block~ : Block)
  ForeignValue(value~ : Value)
  InvalidOperandConstraint
  DuplicateTerminator(block~ : Block)
  BlockAlreadyTerminated(block~ : Block)
  InvalidLayout
} derive(Eq, Debug)

///|
pub impl Show for VCodeBuildError with fn output(self, logger) {
  logger.write_string(Repr(self).to_string())
}

///|
pub suberror VCodeVerifyError {
  EmptyFunction
  MissingTerminator(block~ : Block)
  ForeignValue(instruction~ : Instruction, value~ : Value)
  ForeignBlock(instruction~ : Instruction, block~ : Block)
  InvalidEdgeArity(instruction~ : Instruction, block~ : Block)
  EdgeClassMismatch(instruction~ : Instruction, block~ : Block, index~ : Int)
  InvalidTie(instruction~ : Instruction, operand~ : Int, tied_to~ : Int)
  FixedRegisterClassMismatch(instruction~ : Instruction, operand~ : Int)
  InvalidOperandPreference(instruction~ : Instruction, operand~ : Int)
  DuplicateClobber(instruction~ : Instruction, reg~ : PhysicalReg)
  InvalidSafepointRoot(instruction~ : Instruction, value~ : Value)
  InvalidStackMap(instruction~ : Instruction)
  UnreachableBlock(block~ : Block)
  UseBeforeDefinition(instruction~ : Instruction, value~ : Value)
  DefinitionDoesNotDominate(instruction~ : Instruction, value~ : Value)
  InvalidLayout
} derive(Eq, Debug)

///|
pub impl Show for VCodeVerifyError with fn output(self, logger) {
  logger.write_string(Repr(self).to_string())
}