///|
fn constraint_text(constraint : OperandConstraint) -> String {
  match constraint {
    Any => "any"
    AnyLocation => "any-location"
    Fixed(reg) => "fixed(\{Repr(reg)})"
    TiedTo(index) => "tied(\{index})"
  }
}

///|
fn timing_text(timing : OperandTiming) -> String {
  match timing {
    Early => "early"
    Late => "late"
  }
}

///|
fn operand_text(operand : Operand) -> String {
  let preference = match operand.preference {
    Some(reg) => "(prefer=\{Repr(reg)})"
    None => ""
  }
  "\{Repr(operand.value)}:\{constraint_text(operand.constraint)}\{preference}@\{timing_text(operand.timing)}"
}

///|
pub fn[Inst : Debug] Function::summary(self : Function[Inst]) -> String {
  let mut output = "vcode \{self.name}("
  for index, parameter in self.parameters {
    if index > 0 {
      output += ", "
    }
    output += "\{Repr(parameter)}:\{value_type_text(self.values[parameter.id].ty)}"
  }
  output += ")"
  let result_types = self.result_types()
  if !result_types.is_empty() {
    output += " -> ("
    for index, ty in result_types {
      if index > 0 {
        output += ", "
      }
      output += value_type_text(ty)
    }
    output += ")"
  }
  output += "\n"
  for block in self.layout {
    output += "block\{block.id}"
    let parameters = self.blocks[block.id].parameters
    if parameters.length() > 0 {
      output += "("
      for index, parameter in parameters {
        if index > 0 {
          output += ", "
        }
        output += "\{Repr(parameter)}:\{value_type_text(self.values[parameter.id].ty)}"
      }
      output += ")"
    }
    output += ":\n"
    let instructions = self.blocks[block.id].body.copy()
    match self.blocks[block.id].terminator {
      Some(terminator) => instructions.push(terminator)
      None => ()
    }
    for instruction in instructions {
      let data = self.instructions[instruction.id]
      output += "  i\{instruction.id} \{Repr(data.inst)}"
      let operands = self.instruction_operands(instruction)
      let uses = operands.filter(operand => operand.role == Use)
      let defs = operands.filter(operand => operand.role == Def)
      if uses.length() > 0 {
        output += " uses=["
        for index, operand in uses {
          if index > 0 {
            output += ", "
          }
          output += operand_text(operand)
        }
        output += "]"
      }
      if defs.length() > 0 {
        output += " defs=["
        for index, operand in defs {
          if index > 0 {
            output += ", "
          }
          output += operand_text(operand)
        }
        output += "]"
      }
      let clobbers = self.instruction_clobbers(instruction)
      if clobbers.length() > 0 {
        output += " clobbers=["
        for index, reg in clobbers {
          if index > 0 {
            output += ", "
          }
          output += Repr(reg).to_string()
        }
        output += "]"
      }
      for edge in data.successors {
        output += " -> block\{edge.target.id}("
        for index, argument in edge.arguments {
          if index > 0 {
            output += ", "
          }
          output += Repr(argument).to_string()
        }
        output += ")"
      }
      let metadata = data.metadata
      if metadata.source is Some(source) {
        output += " source=\{Repr(source)}"
      }
      if metadata.trap is Some(trap) {
        output += " trap=\{Repr(trap)}"
      }
      if metadata.safepoint is Some(safepoint) {
        output += " safepoint=\{Repr(safepoint)}"
      }
      if !metadata.live_gc_roots.is_empty() {
        output += " roots=\{Repr(metadata.live_gc_roots)}"
      }
      if metadata.symbol is Some(symbol) {
        output += " symbol=\{Repr(symbol)}"
      }
      if metadata.stack_map is Some(stack_map) {
        output += " stack-map=\{stack_map.id}:\{stack_map.argument_root_count}"
      }
      output += "\n"
    }
  }
  output
}

///|
fn Allocation::slot_text(self : Allocation, slot : StackSlot) -> String {
  let data = self.stack_slots[slot.id]
  "stack\{slot.id}:\{value_type_text(data.ty)}(size=\{data.size}, align=\{data.alignment})"
}

///|
fn Allocation::location_text(self : Allocation, location : Location) -> String {
  match location {
    Register(reg) => Repr(reg).to_string()
    Stack(slot) => self.slot_text(slot)
  }
}

///|
pub fn Allocation::summary(self : Allocation) -> String {
  let mut output = "allocation \{self.function_name}\n"
  for index, location in self.value_locations {
    match location {
      Some(location) =>
        output += "  v\{index}:\{value_type_text(self.value_types[index])} -> \{self.location_text(location)}\n"
      None => ()
    }
  }
  for instruction_index, locations in self.operand_locations {
    for operand_index, location in locations {
      match location {
        Some(reg) =>
          output += "  i\{instruction_index} operand\{operand_index} -> \{Repr(reg)}\n"
        None => ()
      }
    }
  }
  for edit in self.edits {
    match edit.kind {
      Reload(value~, slot~, reg~) =>
        output += "  \{Repr(edit.point.unwrap())}: reload \{Repr(value)} stack\{slot.id} -> \{Repr(reg)}\n"
      Spill(value~, reg~, slot~) =>
        output += "  \{Repr(edit.point.unwrap())}: spill \{Repr(value)} \{Repr(reg)} -> stack\{slot.id}\n"
      Move(value~, from~, to~) =>
        output += "  \{Repr(edit.point.unwrap())}: move \{Repr(value)} \{Repr(from)} -> \{Repr(to)}\n"
      EdgeMove(source~, successor_index~, value~, from~, to~) =>
        output += "  edge(block\{source.id}, \{successor_index}): move \{Repr(value)} \{self.location_text(from)} -> \{self.location_text(to)}\n"
    }
  }
  for root in self.safepoint_roots {
    output += "  i\{root.instruction.id} root \{Repr(root.value)} -> "
    output += match root.location {
      Register(reg) => Repr(reg).to_string()
      Stack(slot) => "stack\{slot.id}"
    }
    output += "\n"
  }
  output
}