///|
/// R15 is reserved from allocation but late physical transfers may write it.
/// Include those writes in the platform-preserved set using the same transfer
/// plans as emission, without adding saves to functions that never use it.
fn frame_saved_registers(
function : @vcode.Function[X64Inst],
allocation : @vcode.Allocation,
) -> Array[@vcode.PhysicalReg] raise X64FrameError {
let saved = @vcode.used_callee_saved_registers(
function, allocation, is_callee_saved,
)
let scratch = int_transfer_scratch()
if !saved.contains(scratch) && transfer_uses_int_scratch(function, allocation) {
saved.push(scratch)
}
saved
}
///|
fn transfer_uses_int_scratch(
function : @vcode.Function[X64Inst],
allocation : @vcode.Allocation,
) -> Bool raise X64FrameError {
let scratch = int_transfer_scratch()
fn uses(plan : @vcode.ResolvedMovePlan) -> Bool {
plan.steps.any(step => {
match step {
Transfer(transfer) => transfer.to == Register(scratch)
SpillScratchToEmergency(scratch=reg, ..)
| ReloadScratchFromEmergency(scratch=reg, ..) => reg == scratch
}
})
}
fn moves_use(moves : Array[@vcode.ParallelMove]) -> Bool raise X64FrameError {
!moves.is_empty() &&
uses(
@vcode.plan_parallel_moves(moves, scratch, fp_transfer_scratch()) catch {
error => raise MovePlanningFailed(cause=error)
},
)
}
for block in function.layout() {
let moves : Array[@vcode.ParallelMove] = []
let mut group = 0
let body = function.block_body(block)
for index in 0..<(body.length() + 1) {
let instruction = if index < body.length() {
body[index]
} else {
function.block_terminator(block).unwrap()
}
let inst = function.instruction(instruction).unwrap()
let next_group = if is_function_input(inst) {
1
} else if is_function_output(inst) {
2
} else if is_incoming_call_result(inst) {
3
} else {
0
}
if next_group != group {
if moves_use(moves) {
return true
}
moves.clear()
group = next_group
}
let transfer = match inst {
IncomingReg(ty, source) =>
Some(
@vcode.ParallelMove::new(
ty,
Register(source),
value_home(function, allocation, instruction, 1),
),
)
IncomingResultArea(source) =>
Some(
@vcode.ParallelMove::new(
Ptr64,
Register(source),
value_home(function, allocation, instruction, 0),
),
)
IncomingCallResult(ty, source) =>
Some(
@vcode.ParallelMove::new(
ty,
Register(source),
value_home(function, allocation, instruction, 0),
),
)
OutgoingReg(ty, destination) =>
Some(
@vcode.ParallelMove::new(
ty,
value_home(function, allocation, instruction, 0),
Register(destination),
),
)
_ => None
}
if transfer is Some(transfer) {
moves.push(transfer)
}
if inst is IncomingCallAreaResult(ty, _) &&
@vcode.reg_class_for_value_type(ty) == Int &&
value_home(function, allocation, instruction, 0) is Stack(_) {
return true
}
if group == 0 {
for placement in [@vcode.PointPlacement::Before, After] {
if !allocation.edits_at(instruction, placement).is_empty() &&
moves_use(
@vcode.allocation_edit_moves(
function, allocation, instruction, placement,
),
) {
return true
}
}
}
let call = instruction_call_plan(inst, allocation, instruction) catch {
error => raise CallPlanningFailed(cause=error)
}
if call is Some(call) && uses(call.register_moves) {
return true
}
}
if moves_use(moves) {
return true
}
let terminator = function.block_terminator(block).unwrap()
for successor in 0.. raise MovePlanningFailed(cause=error)
}
if uses(plan) {
return true
}
}
}
false
}