///|
/// A production builder that establishes local VCode invariants before every
/// mutation. The permissive `Builder` remains available for external tools and
/// verifier negative tests; target selectors use this type so sealing does not
/// need to rediscover local facts by scanning the completed instruction list.
pub struct CheckedBuilder[Inst] {
  priv builder : Builder[Inst]
  priv stack_map_ids : Array[Bool]
  priv mut stack_map_count : Int
}

///|
pub fn[Inst] CheckedBuilder::new(
  name : String,
  parameter_types : Array[ValueType],
) -> CheckedBuilder[Inst] {
  CheckedBuilder::new_with_protocol(name, Platform, parameter_types, [])
}

///|
pub fn[Inst] CheckedBuilder::new_with_results(
  name : String,
  parameter_types : Array[ValueType],
  result_types : Array[ValueType],
) -> CheckedBuilder[Inst] {
  CheckedBuilder::new_with_protocol(
    name,
    Platform,
    parameter_types,
    result_types,
  )
}

///|
pub fn[Inst] CheckedBuilder::new_with_protocol(
  name : String,
  protocol : @native.CallProtocol,
  parameter_types : Array[ValueType],
  result_types : Array[ValueType],
) -> CheckedBuilder[Inst] {
  {
    builder: Builder::new_with_protocol(
      name, protocol, parameter_types, result_types,
    ),
    stack_map_ids: [],
    stack_map_count: 0,
  }
}

///|
pub fn[Inst] CheckedBuilder::entry_block(self : CheckedBuilder[Inst]) -> Block {
  self.builder.entry_block()
}

///|
pub fn[Inst] CheckedBuilder::parameter(
  self : CheckedBuilder[Inst],
  index : Int,
) -> Value raise VCodeBuildError {
  self.builder.parameter(index)
}

///|
pub fn[Inst] CheckedBuilder::create_block(
  self : CheckedBuilder[Inst],
  parameter_types : Array[ValueType],
) -> Block {
  self.builder.create_block(parameter_types)
}

///|
pub fn[Inst] CheckedBuilder::block_parameter(
  self : CheckedBuilder[Inst],
  block : Block,
  index : Int,
) -> Value raise VCodeBuildError {
  self.builder.block_parameter(block, index)
}

///|
fn[Inst] CheckedBuilder::validate_local_facts(
  self : CheckedBuilder[Inst],
  successors : Array[Edge],
  clobbers : Array[PhysicalReg],
  metadata : InstructionMetadata,
) -> Int? raise VCodeBuildError {
  let function = self.builder.function
  for index, reg in clobbers {
    for earlier in 0.. 0 && metadata.safepoint is None {
    raise InvalidSafepointRoot(value=metadata.live_gc_roots[0])
  }
  for root in metadata.live_gc_roots {
    if function.values[root.id].ty != GcRef64 {
      raise InvalidSafepointRoot(value=root)
    }
  }
  match metadata.stack_map {
    Some(stack_map) => {
      if metadata.safepoint is None ||
        stack_map.id < 0 ||
        stack_map.argument_root_count < 0 ||
        (
          stack_map.id < self.stack_map_ids.length() &&
          self.stack_map_ids[stack_map.id]
        ) {
        raise InvalidStackMap
      }
      Some(stack_map.id)
    }
    None => None
  }
}

///|
fn[Inst] CheckedBuilder::record_stack_map(
  self : CheckedBuilder[Inst],
  stack_map_id : Int?,
) -> Unit {
  if stack_map_id is Some(id) {
    while self.stack_map_ids.length() <= id {
      self.stack_map_ids.push(false)
    }
    self.stack_map_ids[id] = true
    self.stack_map_count += 1
  }
}

///|
pub fn[Inst] CheckedBuilder::append_body(
  self : CheckedBuilder[Inst],
  block : Block,
  inst : Inst,
  inputs : Array[Input],
  outputs : Array[Output],
  clobbers : Array[PhysicalReg],
  metadata : InstructionMetadata,
) -> (Instruction, Array[Value]) raise VCodeBuildError {
  self.builder.require_body_open(block)
  let input_tie_ids = self.builder.validate_instruction(
    inputs,
    outputs,
    [],
    metadata,
  )
  let stack_map_id = self.validate_local_facts([], clobbers, metadata)
  let result = self.builder.commit_instruction(
    block,
    inst,
    Body,
    inputs,
    outputs,
    [],
    clobbers,
    metadata,
    input_tie_ids,
  )
  self.record_stack_map(stack_map_id)
  result
}

///|
pub fn[Inst] CheckedBuilder::set_terminator(
  self : CheckedBuilder[Inst],
  block : Block,
  inst : Inst,
  inputs : Array[Input],
  successors : Array[Edge],
  clobbers : Array[PhysicalReg],
  metadata : InstructionMetadata,
) -> Instruction raise VCodeBuildError {
  self.builder.require_terminator_open(block)
  let input_tie_ids = self.builder.validate_instruction(
    inputs,
    [],
    successors,
    metadata,
  )
  let stack_map_id = self.validate_local_facts(successors, clobbers, metadata)
  let instruction = self.builder.commit_instruction(
      block,
      inst,
      Terminator,
      inputs,
      [],
      successors,
      clobbers,
      metadata,
      input_tie_ids,
    ).0
  self.record_stack_map(stack_map_id)
  instruction
}

///|
pub fn[Inst] CheckedBuilder::finish(
  self : CheckedBuilder[Inst],
) -> Function[Inst] raise VCodeBuildError {
  let function = self.builder.function
  for index, block in function.blocks {
    if block.terminator is None {
      raise MissingTerminator(block=Block::new(function.owner, index))
    }
  }
  if self.stack_map_count != self.stack_map_ids.length() {
    raise InvalidStackMap
  }
  function
}