///|
priv enum CallTransferDestination {
TransferRegister(PhysicalReg)
TransferOutgoingStack(Int)
}
///|
/// One allocated source and its physical destination in a target call layout.
pub struct CallTransfer {
priv ty : ValueType
priv source : Location
priv destination : CallTransferDestination
}
///|
pub fn CallTransfer::to_register(
ty : ValueType,
source : Location,
destination : PhysicalReg,
) -> CallTransfer {
{ ty, source, destination: TransferRegister(destination) }
}
///|
pub fn CallTransfer::to_stack(
ty : ValueType,
source : Location,
offset : Int,
) -> CallTransfer {
{ ty, source, destination: TransferOutgoingStack(offset) }
}
///|
pub struct StackArgumentTransfer {
ty : ValueType
source : Location
offset : Int
scratch : PhysicalReg?
} derive(Eq, Debug)
///|
pub struct CallTransferPlan {
stack_transfers : Array[StackArgumentTransfer]
register_moves : Array[ParallelMove]
} derive(Eq, Debug)
///|
pub struct ResolvedCallTransferPlan {
stack_transfers : Array[StackArgumentTransfer]
register_moves : ResolvedMovePlan
} derive(Eq, Debug)
///|
pub suberror CallTransferError {
InvalidSourceClass(index~ : Int)
ForeignSourceStack(index~ : Int)
InvalidSourceStackType(
index~ : Int,
expected~ : ValueType,
actual~ : ValueType
)
InvalidSourceStackLayout(index~ : Int)
InvalidDestinationClass(index~ : Int)
InvalidStackOffset(index~ : Int, offset~ : Int)
InvalidOutgoingStackRange(start~ : Int, size~ : Int)
OutgoingStackDestinationOutOfRange(index~ : Int, offset~ : Int)
DuplicateRegisterDestination(index~ : Int, register~ : PhysicalReg)
OverlappingStackDestination(first~ : Int, second~ : Int)
ProtectedLocationOverwrite(index~ : Int, location~ : Location)
MoveResolutionFailed(cause~ : MoveResolveError)
} derive(Eq, Debug)
///|
fn transfer_size(ty : ValueType) -> Int {
match ty {
I32 | F32 => 4
I64 | F64 | Ptr64 | GcRef64 => 8
V128 => 16
}
}
///|
fn ranges_overlap(
left : Int,
left_size : Int,
right : Int,
right_size : Int,
) -> Bool {
left < right + right_size && right < left + left_size
}
///|
fn stack_transfer_scratch(
ty : ValueType,
scratch_int : Array[PhysicalReg],
scratch_fp : Array[PhysicalReg],
reserved : Array[Location],
) -> PhysicalReg raise CallTransferError {
let candidates = if reg_class_for_value_type(ty) == Int {
scratch_int
} else {
scratch_fp
}
let expected = reg_class_for_value_type(ty)
for scratch in candidates {
if scratch.class != expected {
raise MoveResolutionFailed(cause=InvalidScratch(reg=scratch, expected~))
}
if !reserved.contains(Register(scratch)) {
return scratch
}
}
raise MoveResolutionFailed(
cause=MissingScratch(class=reg_class_for_value_type(ty)),
)
}
///|
/// Build one atomic call setup from allocated sources.
///
/// Outgoing stack arguments are captured before any ABI register destination is
/// overwritten. Register destinations are then resolved as one parallel move.
fn prepare_call_transfers(
allocation : Allocation,
transfers : Array[CallTransfer],
outgoing_stack_start : Int,
outgoing_stack_size : Int,
scratch_int : Array[PhysicalReg],
scratch_fp : Array[PhysicalReg],
protected_locations : Array[Location],
) -> CallTransferPlan raise CallTransferError {
if outgoing_stack_start < 0 || outgoing_stack_size < 0 {
raise InvalidOutgoingStackRange(
start=outgoing_stack_start,
size=outgoing_stack_size,
)
}
let outgoing_stack_end = outgoing_stack_start + outgoing_stack_size
if outgoing_stack_end < outgoing_stack_start {
raise InvalidOutgoingStackRange(
start=outgoing_stack_start,
size=outgoing_stack_size,
)
}
let stack_transfers : Array[StackArgumentTransfer] = []
let register_moves : Array[ParallelMove] = []
let register_destinations : Array[PhysicalReg] = []
let stack_indices : Array[Int] = []
let scratch_reserved = protected_locations.copy()
for transfer in transfers {
if transfer.source is Register(source) &&
!scratch_reserved.contains(Register(source)) {
scratch_reserved.push(Register(source))
}
if transfer.destination is TransferRegister(destination) &&
!scratch_reserved.contains(Register(destination)) {
scratch_reserved.push(Register(destination))
}
}
for index, transfer in transfers {
match transfer.source {
Register(source) =>
if source.class != reg_class_for_value_type(transfer.ty) {
raise InvalidSourceClass(index~)
}
Stack(slot) =>
match allocation.stack_slot_type(slot) {
None => raise ForeignSourceStack(index~)
Some(actual) => {
if actual != transfer.ty {
raise InvalidSourceStackType(
index~,
expected=transfer.ty,
actual~,
)
}
let size = transfer_size(transfer.ty)
if allocation.stack_slot_size(slot).unwrap() < size ||
allocation.stack_slot_alignment(slot).unwrap() < size {
raise InvalidSourceStackLayout(index~)
}
}
}
}
match transfer.destination {
TransferRegister(destination) => {
if destination.class != reg_class_for_value_type(transfer.ty) {
raise InvalidDestinationClass(index~)
}
if register_destinations.contains(destination) {
raise DuplicateRegisterDestination(index~, register=destination)
}
register_destinations.push(destination)
let destination_location = Register(destination)
if protected_locations.contains(destination_location) &&
transfer.source != destination_location {
raise ProtectedLocationOverwrite(
index~,
location=destination_location,
)
}
register_moves.push(
ParallelMove::new(transfer.ty, transfer.source, destination_location),
)
}
TransferOutgoingStack(offset) => {
let size = transfer_size(transfer.ty)
if offset < 0 || offset % size != 0 {
raise InvalidStackOffset(index~, offset~)
}
let end = offset + size
if offset < outgoing_stack_start ||
end < offset ||
end > outgoing_stack_end {
raise OutgoingStackDestinationOutOfRange(index~, offset~)
}
for previous in stack_indices {
let previous_transfer = transfers[previous]
guard previous_transfer.destination
is TransferOutgoingStack(previous_offset) else {
continue
}
if ranges_overlap(
offset,
size,
previous_offset,
transfer_size(previous_transfer.ty),
) {
raise OverlappingStackDestination(first=previous, second=index)
}
}
stack_indices.push(index)
let scratch = match transfer.source {
Register(_) => None
Stack(_) =>
Some(
stack_transfer_scratch(
transfer.ty,
scratch_int,
scratch_fp,
scratch_reserved,
),
)
}
stack_transfers.push({
ty: transfer.ty,
source: transfer.source,
offset,
scratch,
})
}
}
}
{ stack_transfers, register_moves }
}
///|
pub fn plan_call_transfers(
allocation : Allocation,
transfers : Array[CallTransfer],
outgoing_stack_start : Int,
outgoing_stack_size : Int,
scratch_int : Array[PhysicalReg],
scratch_fp : Array[PhysicalReg],
protected_locations : Array[Location],
) -> CallTransferPlan raise CallTransferError {
let transfer_plan = prepare_call_transfers(
allocation, transfers, outgoing_stack_start, outgoing_stack_size, scratch_int,
scratch_fp, protected_locations,
)
let register_moves = resolve_parallel_moves(
transfer_plan.register_moves,
scratch_int,
scratch_fp,
) catch {
error => raise MoveResolutionFailed(cause=error)
}
{ stack_transfers: transfer_plan.stack_transfers, register_moves }
}
///|
pub fn plan_resolved_call_transfers(
allocation : Allocation,
transfers : Array[CallTransfer],
outgoing_stack_start : Int,
outgoing_stack_size : Int,
stack_scratch_int : Array[PhysicalReg],
stack_scratch_fp : Array[PhysicalReg],
move_scratch_int : PhysicalReg,
move_scratch_fp : PhysicalReg,
protected_locations : Array[Location],
) -> ResolvedCallTransferPlan raise CallTransferError {
let transfer_plan = prepare_call_transfers(
allocation, transfers, outgoing_stack_start, outgoing_stack_size, stack_scratch_int,
stack_scratch_fp, protected_locations,
)
let register_moves = plan_parallel_moves(
transfer_plan.register_moves,
move_scratch_int,
move_scratch_fp,
) catch {
error => raise MoveResolutionFailed(cause=error)
}
{ stack_transfers: transfer_plan.stack_transfers, register_moves }
}