///|
fn DirectBuilder::frame_emit(
  self : DirectBuilder,
  operation : Operation,
  operands : Array[Value],
  results : Array[@native.ValueType],
) -> Array[Value] {
  self.emit_raw(operation, operands, results, InstructionMetadata::empty())
}

///|
fn DirectBuilder::frame_load(
  self : DirectBuilder,
  base : Value,
  offset : Int32,
) -> Value {
  let (base, offset) = if offset < 0 {
    let bits = self.frame_emit(Convert(Bitcast(Ptr64, I64)), [base], [I64])[0]
    let bits = self.frame_emit(
        IntBinaryImmediate(Add, offset.to_int64().reinterpret_as_uint64()),
        [bits],
        [I64],
      )[0]
    (self.frame_emit(Convert(Bitcast(I64, Ptr64)), [bits], [Ptr64])[0], 0)
  } else {
    (base, offset)
  }
  self.frame_emit(
    Load(LoadSpec::new(W64, None, Ptr64, offset.to_uint64(), Little, None)),
    [base],
    [Ptr64],
  )[0]
}

///|
fn DirectBuilder::frame_store(
  self : DirectBuilder,
  base : Value,
  offset : Int32,
  value : Value,
  type_ : @native.ValueType,
) -> Unit {
  self.frame_emit(
    Store(StoreSpec::new(W64, type_, offset.to_uint64(), Little, None)),
    [base, value],
    [],
  )
  |> ignore
}

///|
fn DirectBuilder::frame_guard(
  self : DirectBuilder,
  pointer : Value,
  slow : Block,
) -> Unit {
  let next = self.create_block([])
  let bits = self.frame_emit(Convert(Bitcast(Ptr64, I64)), [pointer], [I64])[0]
  self.branch_int_compare_immediate(NotEqual, bits, 0UL, next, [], slow, [])
  self.switch_to_block(next)
}

///|
fn DirectBuilder::frame_execution(
  self : DirectBuilder,
  environment : Value,
  layout : InlineFunctionRootFrameLayout,
  slow : Block,
) -> Value {
  self.frame_guard(environment, slow)
  let execution = self.frame_load(environment, layout.execution_offset)
  self.frame_guard(execution, slow)
  let active = self.frame_load(execution, layout.active_offset)
  self.frame_guard(active, slow)
  execution
}

///|
/// Emit only the admitted non-calling path, then select the original helper
/// block. Root publication, precise masks and unwind markers use the same frame.
fn DirectBuilder::prepare_inline_frame_enter(
  self : DirectBuilder,
  environment : Value,
  header : Value,
  roots : Value,
  mask : Value,
  count : Int32,
  layout : InlineFunctionRootFrameLayout,
) -> Block {
  let slow = self.create_block([])
  let ready = self.create_block([])
  let execution = self.frame_execution(environment, layout, slow)
  let previous = self.frame_load(execution, layout.head_offset)
  self.frame_store(header, 0, previous, Ptr64)
  self.frame_store(header, 8, roots, Ptr64)
  self.frame_store(header, 16, mask, Ptr64)
  let count = self.frame_emit(I64Const(count.to_uint64()), [], [I64])[0]
  self.frame_store(header, 24, count, I64)
  self.frame_store(execution, layout.head_offset, header, Ptr64)
  self.jump(ready, [])
  self.switch_to_block(slow)
  ready
}

///|
/// A mismatched chain head always selects the original trapping helper.
fn DirectBuilder::prepare_inline_frame_leave(
  self : DirectBuilder,
  environment : Value,
  header : Value,
  layout : InlineFunctionRootFrameLayout,
) -> Block {
  let slow = self.create_block([])
  let ready = self.create_block([])
  let execution = self.frame_execution(environment, layout, slow)
  let current = self.frame_load(execution, layout.head_offset)
  let current = self.frame_emit(Convert(Bitcast(Ptr64, I64)), [current], [I64])[0]
  let expected = self.frame_emit(Convert(Bitcast(Ptr64, I64)), [header], [I64])[0]
  let matching = self.create_block([])
  self.branch_int_compare(Equal, current, expected, matching, [], slow, [])
  self.switch_to_block(matching)
  let previous = self.frame_load(header, 0)
  self.frame_store(execution, layout.head_offset, previous, Ptr64)
  self.jump(ready, [])
  self.switch_to_block(slow)
  ready
}