///|
/// Receive one contiguous group of ABI inputs. Register transfers must finish
/// before stack loads can reuse the target's scratch registers. The target
/// describes each pseudo as (type, incoming location, value-home operand).
pub fn[Inst, Buffer, Frame, E : Error] emit_incoming_values(
  function : Function[Inst],
  allocation : Allocation,
  body : ArrayView[Instruction],
  current_index : Int32,
  describe : (Inst) -> (ValueType, CallArgumentLocation, Int32)?,
  buffer : Buffer,
  frame : Frame,
  stack_base : Int32?,
  slot_offset : (Frame, StackSlot) -> Int32?,
  stack_scratch : (ValueType) -> PhysicalReg,
  int_scratch : PhysicalReg,
  fp_scratch : PhysicalReg,
  emit_moves : (Buffer, Frame, ResolvedMovePlan) -> Unit raise E,
  access_stack : (Buffer, Bool, ValueType, PhysicalReg, Int32) -> Unit raise E,
  invalid_moves : (MoveResolveError) -> E,
  missing_stack_area : E,
) -> Unit raise E {
  if current_index > 0 &&
    describe(function.instruction(body[current_index - 1]).unwrap()) is Some(_) {
    return
  }
  let register_moves : Array[ParallelMove] = []
  let stack_values : Array[(ValueType, Location, Int32)] = []
  for index in current_index..
        register_moves.push(ParallelMove::new(ty, Register(reg), home))
      CallStack(offset) => stack_values.push((ty, home, offset))
    }
  }
  let resolved = plan_parallel_moves(register_moves, int_scratch, fp_scratch) catch {
    error => raise invalid_moves(error)
  }
  emit_moves(buffer, frame, resolved)
  guard stack_base is Some(base) else {
    if !stack_values.is_empty() {
      raise missing_stack_area
    }
    return
  }
  for (ty, home, offset) in stack_values {
    match home {
      Register(destination) =>
        access_stack(buffer, true, ty, destination, base + offset)
      Stack(slot) => {
        let scratch = stack_scratch(ty)
        access_stack(buffer, true, ty, scratch, base + offset)
        access_stack(
          buffer,
          false,
          ty,
          scratch,
          slot_offset(frame, slot).unwrap(),
        )
      }
    }
  }
}

///|
/// Apply resolved physical moves using the target's register and frame
/// instructions. Parallel moves reach this stage in dependency order.
pub fn[Buffer, Frame, E : Error] emit_resolved_moves(
  plan : ResolvedMovePlan,
  buffer : Buffer,
  frame : Frame,
  slot_offset : (Frame, StackSlot) -> Int32?,
  emergency_offset : (Frame) -> Int32?,
  move_register : (Buffer, ValueType, PhysicalReg, PhysicalReg) -> Unit,
  access_stack : (Buffer, Bool, ValueType, PhysicalReg, Int32) -> Unit raise E,
  unresolved_stack_move : E,
  missing_emergency_area : E,
) -> Unit raise E {
  for step in plan.steps {
    match step {
      Transfer(transfer) =>
        match (transfer.from, transfer.to) {
          (Register(from), Register(to)) =>
            move_register(buffer, transfer.ty, to, from)
          (Stack(slot), Register(to)) =>
            access_stack(
              buffer,
              true,
              transfer.ty,
              to,
              slot_offset(frame, slot).unwrap(),
            )
          (Register(from), Stack(slot)) =>
            access_stack(
              buffer,
              false,
              transfer.ty,
              from,
              slot_offset(frame, slot).unwrap(),
            )
          (Stack(_), Stack(_)) => raise unresolved_stack_move
        }
      SpillScratchToEmergency(ty~, scratch~)
      | ReloadScratchFromEmergency(ty~, scratch~) => {
        guard emergency_offset(frame) is Some(offset) else {
          raise missing_emergency_area
        }
        access_stack(
          buffer,
          step is ReloadScratchFromEmergency(..),
          ty,
          scratch,
          offset,
        )
      }
    }
  }
}

///|
/// Copy stack arguments before register moves can overwrite their sources.
pub fn[Buffer, Frame, E : Error] emit_call_stack_transfers(
  plan : ResolvedCallTransferPlan,
  buffer : Buffer,
  frame : Frame,
  slot_offset : (Frame, StackSlot) -> Int32?,
  access_stack : (Buffer, Bool, ValueType, PhysicalReg, Int32) -> Unit raise E,
) -> Unit raise E {
  for transfer in plan.stack_transfers {
    let source = match transfer.source {
      Register(reg) => reg
      Stack(slot) => {
        let scratch = transfer.scratch.unwrap()
        access_stack(
          buffer,
          true,
          transfer.ty,
          scratch,
          slot_offset(frame, slot).unwrap(),
        )
        scratch
      }
    }
    // Integer call slots are eight bytes. Write the full slot so its upper
    // half is canonical for callees that consume the native-width value.
    let stack_type : ValueType = if transfer.ty is I32 {
      I64
    } else {
      transfer.ty
    }
    access_stack(buffer, false, stack_type, source, transfer.offset)
  }
}