ABI: implement register arguments with constraints. (#4858)
* ABI: implement register arguments with constraints. Currently, Cranelift's ABI code emits a sequence of moves from physical registers into vregs at the top of the function body, one for every register-carried argument. For a number of reasons, we want to move to operand constraints instead, and remove the use of explicitly-named "pinned vregs"; this allows for better regalloc in theory, as it removes the need to "reverse-engineer" the sequence of moves. This PR alters the ABI code so that it generates a single "args" pseudo-instruction as the first instruction in the function body. This pseudo-inst defs all register arguments, and constrains them to the appropriate registers at the def-point. Subsequently the regalloc can move them wherever it needs to. Some care was taken not to have this pseudo-inst show up in post-regalloc disassemblies, but the change did cause a general regalloc "shift" in many tests, so the precise-output updates are a bit noisy. Sorry about that! A subsequent PR will handle the other half of the ABI code, namely, the callsite case, with a similar preg-to-constraint conversion. * Update based on review feedback. * Review feedback.
This commit is contained in:
@@ -124,6 +124,17 @@ use std::mem;
|
||||
/// a small fixed sequence implementing one operation.
|
||||
pub type SmallInstVec<I> = SmallVec<[I; 4]>;
|
||||
|
||||
/// A type used by backends to track argument-binding info in the "args"
|
||||
/// pseudoinst. The pseudoinst holds a vec of `ArgPair` structs.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArgPair {
|
||||
/// The vreg that is defined by this args pseudoinst.
|
||||
pub vreg: Writable<Reg>,
|
||||
/// The preg that the arg arrives in; this constrains the vreg's
|
||||
/// placement at the pseudoinst.
|
||||
pub preg: Reg,
|
||||
}
|
||||
|
||||
/// A location for (part of) an argument or return value. These "storage slots"
|
||||
/// are specified for each register-sized part of an argument.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -364,6 +375,10 @@ pub trait ABIMachineSpec {
|
||||
to_bits: u8,
|
||||
) -> Self::I;
|
||||
|
||||
/// Generate an "args" pseudo-instruction to capture input args in
|
||||
/// registers.
|
||||
fn gen_args(isa_flags: &Self::F, args: Vec<ArgPair>) -> Self::I;
|
||||
|
||||
/// Generate a return instruction.
|
||||
fn gen_ret(setup_frame: bool, isa_flags: &Self::F, rets: Vec<Reg>) -> Self::I;
|
||||
|
||||
@@ -856,6 +871,9 @@ pub struct Callee<M: ABIMachineSpec> {
|
||||
stackslots_size: u32,
|
||||
/// Stack size to be reserved for outgoing arguments.
|
||||
outgoing_args_size: u32,
|
||||
/// Register-argument defs, to be provided to the `args`
|
||||
/// pseudo-inst, and pregs to constrain them to.
|
||||
reg_args: Vec<ArgPair>,
|
||||
/// Clobbered registers, from regalloc.
|
||||
clobbered: Vec<Writable<RealReg>>,
|
||||
/// Total number of spillslots, including for 'dynamic' types, from regalloc.
|
||||
@@ -1012,6 +1030,7 @@ impl<M: ABIMachineSpec> Callee<M> {
|
||||
sized_stackslots,
|
||||
stackslots_size,
|
||||
outgoing_args_size: 0,
|
||||
reg_args: vec![],
|
||||
clobbered: vec![],
|
||||
spillslots: None,
|
||||
fixed_frame_storage_size: 0,
|
||||
@@ -1283,7 +1302,7 @@ impl<M: ABIMachineSpec> Callee<M> {
|
||||
/// Generate an instruction which copies an argument to a destination
|
||||
/// register.
|
||||
pub fn gen_copy_arg_to_regs(
|
||||
&self,
|
||||
&mut self,
|
||||
sigs: &SigSet,
|
||||
idx: usize,
|
||||
into_regs: ValueRegs<Writable<Reg>>,
|
||||
@@ -1291,10 +1310,16 @@ impl<M: ABIMachineSpec> Callee<M> {
|
||||
let mut insts = smallvec![];
|
||||
let mut copy_arg_slot_to_reg = |slot: &ABIArgSlot, into_reg: &Writable<Reg>| {
|
||||
match slot {
|
||||
&ABIArgSlot::Reg { reg, ty, .. } => {
|
||||
// Extension mode doesn't matter (we're copying out, not in; we
|
||||
// ignore high bits by convention).
|
||||
insts.push(M::gen_move(*into_reg, reg.into(), ty));
|
||||
&ABIArgSlot::Reg { reg, .. } => {
|
||||
// Add a preg -> def pair to the eventual `args`
|
||||
// instruction. Extension mode doesn't matter
|
||||
// (we're copying out, not in; we ignore high bits
|
||||
// by convention).
|
||||
let arg = ArgPair {
|
||||
vreg: *into_reg,
|
||||
preg: reg.into(),
|
||||
};
|
||||
self.reg_args.push(arg);
|
||||
}
|
||||
&ABIArgSlot::Stack {
|
||||
offset,
|
||||
@@ -1481,17 +1506,18 @@ impl<M: ABIMachineSpec> Callee<M> {
|
||||
/// values or an otherwise large return value that must be passed on the
|
||||
/// stack; typically the ABI specifies an extra hidden argument that is a
|
||||
/// pointer to that memory.
|
||||
pub fn gen_retval_area_setup(&self, sigs: &SigSet) -> Option<M::I> {
|
||||
pub fn gen_retval_area_setup(&mut self, sigs: &SigSet) -> Option<M::I> {
|
||||
if let Some(i) = sigs[self.sig].stack_ret_arg {
|
||||
let insts =
|
||||
self.gen_copy_arg_to_regs(sigs, i, ValueRegs::one(self.ret_area_ptr.unwrap()));
|
||||
let inst = insts.into_iter().next().unwrap();
|
||||
trace!(
|
||||
"gen_retval_area_setup: inst {:?}; ptr reg is {:?}",
|
||||
inst,
|
||||
self.ret_area_ptr.unwrap().to_reg()
|
||||
);
|
||||
Some(inst)
|
||||
insts.into_iter().next().map(|inst| {
|
||||
trace!(
|
||||
"gen_retval_area_setup: inst {:?}; ptr reg is {:?}",
|
||||
inst,
|
||||
self.ret_area_ptr.unwrap().to_reg()
|
||||
);
|
||||
inst
|
||||
})
|
||||
} else {
|
||||
trace!("gen_retval_area_setup: not needed");
|
||||
None
|
||||
@@ -1573,6 +1599,23 @@ impl<M: ABIMachineSpec> Callee<M> {
|
||||
|
||||
gen_store_stack_multi::<M>(StackAMode::NominalSPOffset(sp_off, ty), from_regs, ty)
|
||||
}
|
||||
|
||||
/// Get an `args` pseudo-inst, if any, that should appear at the
|
||||
/// very top of the function body prior to regalloc.
|
||||
pub fn take_args(&mut self) -> Option<M::I> {
|
||||
if self.reg_args.len() > 0 {
|
||||
// Very first instruction is an `args` pseudo-inst that
|
||||
// establishes live-ranges for in-register arguments and
|
||||
// constrains them at the start of the function to the
|
||||
// locations defined by the ABI.
|
||||
Some(M::gen_args(
|
||||
&self.isa_flags,
|
||||
std::mem::take(&mut self.reg_args),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### Post-Regalloc Functions
|
||||
|
||||
Reference in New Issue
Block a user