Assign call arguments to stack slots.

When making an outgoing call, some arguments may have to be passed on
the stack. Allocate OutgoingArg stack slots for these arguments and
write them immediately before the outgoing call instruction.

Do the same for incoming function arguments on the stack, but use
IncomingArg stack slots instead. This was previously done in the
spiller, but we move it to the legalizer so it is done at the same time
as outgoing stack arguments.

These stack slot assignments are done in the legalizer before live
range analysis because the outgoing arguments usually are in different
SSSA values with their own short live ranges.
This commit is contained in:
Jakob Stoklund Olesen
2017-08-01 13:32:30 -07:00
parent d536888725
commit 6dc5b3e608
5 changed files with 118 additions and 33 deletions

View File

@@ -3,6 +3,8 @@ test legalizer
isa riscv
; regex: V=v\d+
; regex: SS=ss\d+
; regex: WS=\s+
function %int_split_args(i64) -> i64 {
ebb0(v0: i64):
@@ -118,3 +120,15 @@ ebb0(v0: i32, v1: f32x2):
; check: call_indirect $sig1, $v0($V, $V)
return
}
; Call a function that takes arguments on the stack.
function %stack_args(i32) {
; check: $(ss0=$SS) = outgoing_arg 4
fn1 = function %foo(i64, i64, i64, i64, i32)
ebb0(v0: i32):
v1 = iconst.i64 1
call fn1(v1, v1, v1, v1, v0)
; check: [GPsp#48,$ss0]$WS $(v0s=$V) = spill $v0
; check: call $fn1($(=.*), $v0s)
return
}

View File

@@ -51,7 +51,7 @@ impl Signature {
let bytes = self.argument_types
.iter()
.filter_map(|arg| match arg.location {
ArgumentLoc::Stack(offset) if offset > 0 => {
ArgumentLoc::Stack(offset) if offset >= 0 => {
Some(offset as u32 + arg.value_type.bytes())
}
_ => None,

View File

@@ -20,7 +20,8 @@
use abi::{legalize_abi_value, ValueConversion};
use flowgraph::ControlFlowGraph;
use ir::{Function, Cursor, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, Signature, SigRef,
ArgumentType, ArgumentPurpose};
ArgumentType, ArgumentPurpose, ArgumentLoc, ValueLoc, ValueLocations, StackSlots,
StackSlotKind};
use ir::instructions::CallInfo;
use isa::TargetIsa;
use legalizer::split::{isplit, vsplit};
@@ -32,12 +33,15 @@ use legalizer::split::{isplit, vsplit};
/// in a state with type discrepancies.
pub fn legalize_signatures(func: &mut Function, isa: &TargetIsa) {
isa.legalize_signature(&mut func.signature, true);
func.signature.compute_argument_bytes();
for sig in func.dfg.signatures.keys() {
isa.legalize_signature(&mut func.dfg.signatures[sig], false);
func.dfg.signatures[sig].compute_argument_bytes();
}
if let Some(entry) = func.layout.entry_block() {
legalize_entry_arguments(func, entry);
spill_entry_arguments(func, entry);
}
}
@@ -448,13 +452,18 @@ fn legalize_inst_arguments<ArgType>(dfg: &mut DataFlowGraph,
/// original return values. The call's result values will be adapted to match the new signature.
///
/// Returns `true` if any instructions were inserted.
pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mut Cursor) -> bool {
pub fn handle_call_abi(dfg: &mut DataFlowGraph,
locations: &mut ValueLocations,
stack_slots: &mut StackSlots,
cfg: &ControlFlowGraph,
pos: &mut Cursor)
-> bool {
let mut inst = pos.current_inst()
.expect("Cursor must point to a call instruction");
// Start by checking if the argument types already match the signature.
let sig_ref = match check_call_signature(dfg, inst) {
Ok(_) => return false,
Ok(_) => return spill_call_arguments(dfg, locations, stack_slots, pos),
Err(s) => s,
};
@@ -478,6 +487,10 @@ pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mu
sig_ref,
dfg.signatures[sig_ref]);
// Go back and insert spills for any stack arguments.
pos.goto_inst(inst);
spill_call_arguments(dfg, locations, stack_slots, pos);
// Yes, we changed stuff.
true
}
@@ -556,3 +569,83 @@ pub fn handle_return_abi(dfg: &mut DataFlowGraph,
// Yes, we changed stuff.
true
}
/// Assign stack slots to incoming function arguments on the stack.
///
/// Values that are passed into the function on the stack must be assigned to an `IncomingArg`
/// stack slot already during legalization.
fn spill_entry_arguments(func: &mut Function, entry: Ebb) {
for (abi, &arg) in func.signature
.argument_types
.iter()
.zip(func.dfg.ebb_args(entry)) {
if let ArgumentLoc::Stack(offset) = abi.location {
let ss = func.stack_slots.make_incoming_arg(abi.value_type, offset);
*func.locations.ensure(arg) = ValueLoc::Stack(ss);
}
}
}
/// Assign stack slots to outgoing function arguments on the stack.
///
/// Values that are passed to a called function on the stack must be assigned to a matching
/// `OutgoingArg` stack slot. The assignment must happen immediately before the call.
///
/// TODO: The outgoing stack slots can be written a bit earlier, as long as there are no branches
/// or calls between writing the stack slots and the call instruction. Writing the slots earlier
/// could help reduce register pressure before the call.
fn spill_call_arguments(dfg: &mut DataFlowGraph,
locations: &mut ValueLocations,
stack_slots: &mut StackSlots,
pos: &mut Cursor)
-> bool {
let inst = pos.current_inst()
.expect("Cursor must point to a call instruction");
let sig_ref = dfg.call_signature(inst)
.expect("Call instruction expected.");
// Start by building a list of stack slots and arguments to be replaced.
// This requires borrowing `dfg`, so we can't change anything.
let arglist = dfg.inst_variable_args(inst)
.iter()
.zip(&dfg.signatures[sig_ref].argument_types)
.enumerate()
.filter_map(|(idx, (&arg, abi))| {
match abi.location {
ArgumentLoc::Stack(offset) => {
// Is `arg` already in the right kind of stack slot?
match locations.get(arg) {
Some(&ValueLoc::Stack(ss)) => {
// We won't reassign `arg` to a different stack slot. Assert out of
// the stack slot is wrong.
assert_eq!(stack_slots[ss].kind, StackSlotKind::OutgoingArg);
assert_eq!(stack_slots[ss].offset, offset);
assert_eq!(stack_slots[ss].size, abi.value_type.bytes());
None
}
_ => {
// Assign `arg` to a new stack slot.
let ss = stack_slots.get_outgoing_arg(abi.value_type, offset);
Some((idx, arg, ss))
}
}
}
_ => None,
}
})
.collect::<Vec<_>>();
if arglist.is_empty() {
return false;
}
// Insert the spill instructions and rewrite call arguments.
for (idx, arg, ss) in arglist {
let stack_val = dfg.ins(pos).spill(arg);
*locations.ensure(stack_val) = ValueLoc::Stack(ss);
dfg.inst_variable_args_mut(inst)[idx] = stack_val;
}
// We changed stuff.
true
}

View File

@@ -51,7 +51,12 @@ pub fn legalize_function(func: &mut Function,
let opcode = func.dfg[inst].opcode();
// Check for ABI boundaries that need to be converted to the legalized signature.
if opcode.is_call() && boundary::handle_call_abi(&mut func.dfg, cfg, &mut pos) {
if opcode.is_call() &&
boundary::handle_call_abi(&mut func.dfg,
&mut func.locations,
&mut func.stack_slots,
cfg,
&mut pos) {
// Go back and legalize the inserted argument conversion instructions.
pos.set_position(prev_pos);
continue;

View File

@@ -17,7 +17,7 @@
use dominator_tree::DominatorTree;
use ir::{DataFlowGraph, Layout, Cursor, InstBuilder};
use ir::{Function, Ebb, Inst, Value, ValueLoc, ArgumentLoc, Signature, SigRef};
use ir::{Function, Ebb, Inst, Value, ValueLoc, SigRef};
use ir::{InstEncodings, StackSlots, ValueLocations};
use isa::registers::{RegClassMask, RegClassIndex};
use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind};
@@ -46,7 +46,6 @@ struct Context<'a> {
encodings: &'a mut InstEncodings,
stack_slots: &'a mut StackSlots,
locations: &'a mut ValueLocations,
func_signature: &'a Signature,
// References to contextual data structures we need.
domtree: &'a DominatorTree,
@@ -94,7 +93,6 @@ impl Spilling {
encodings: &mut func.encodings,
stack_slots: &mut func.stack_slots,
locations: &mut func.locations,
func_signature: &func.signature,
domtree,
liveness,
virtregs,
@@ -112,37 +110,12 @@ impl<'a> Context<'a> {
layout: &mut Layout,
dfg: &mut DataFlowGraph,
tracker: &mut LiveValueTracker) {
if let Some(entry) = layout.entry_block() {
self.spill_entry_arguments(entry, dfg);
}
self.topo.reset(layout.ebbs());
while let Some(ebb) = self.topo.next(layout, self.domtree) {
self.visit_ebb(ebb, layout, dfg, tracker);
}
}
/// Assign stack slots to incoming function arguments on the stack.
fn spill_entry_arguments(&mut self, entry: Ebb, dfg: &DataFlowGraph) {
for (abi, &arg) in self.func_signature
.argument_types
.iter()
.zip(dfg.ebb_args(entry)) {
if let ArgumentLoc::Stack(offset) = abi.location {
// Function arguments passed on the stack can't be part of a virtual register. We
// would need to write other values to the stack slot, but it belongs to the
// caller. (Not that the caller would care, nobody depends on stack arguments being
// preserved across calls).
assert_eq!(self.virtregs.get(arg),
None,
"Stack argument {} can't be part of a virtual register",
arg);
let ss = self.stack_slots.make_incoming_arg(abi.value_type, offset);
*self.locations.ensure(arg) = ValueLoc::Stack(ss);
}
}
}
fn visit_ebb(&mut self,
ebb: Ebb,
layout: &mut Layout,