Make spilling visit fallthrough_return instructions too.

This is a followup to af2a952aabd82cf401cc664d0262b139ff92d86b. It
teaches the spilling pass to use the is_ghost() property to test whether
to visit instructions. This fixes a bug handling multiple return values
with fallthrough_return.
This commit is contained in:
Dan Gohman
2018-11-06 11:14:14 -08:00
committed by Benjamin Bouvier
parent ef2e11265c
commit cd7c57e598
2 changed files with 64 additions and 50 deletions

View File

@@ -11,3 +11,13 @@ ebb0:
; check: v2 = iconst.i64 0 ; check: v2 = iconst.i64 0
; check: v3 = copy v2 ; check: v3 = copy v2
; check: return v2, v3 ; check: return v2, v3
; Same thing, now with a fallthrough_return.
function %multiple_returns() -> i64, i64 {
ebb0:
v2 = iconst.i64 0
fallthrough_return v2, v2
}
; check: v2 = iconst.i64 0
; check: v3 = copy v2
; check: fallthrough_return v2, v3

View File

@@ -134,11 +134,8 @@ impl<'a> Context<'a> {
self.process_spills(tracker); self.process_spills(tracker);
while let Some(inst) = self.cur.next_inst() { while let Some(inst) = self.cur.next_inst() {
if let Some(constraints) = self if !self.cur.func.dfg[inst].opcode().is_ghost() {
.encinfo self.visit_inst(inst, ebb, tracker);
.operand_constraints(self.cur.func.encodings[inst])
{
self.visit_inst(inst, ebb, constraints, tracker);
} else { } else {
let (_throughs, kills) = tracker.process_ghost(inst); let (_throughs, kills) = tracker.process_ghost(inst);
self.free_regs(kills); self.free_regs(kills);
@@ -237,17 +234,15 @@ impl<'a> Context<'a> {
self.free_dead_regs(params); self.free_dead_regs(params);
} }
fn visit_inst( fn visit_inst(&mut self, inst: Inst, ebb: Ebb, tracker: &mut LiveValueTracker) {
&mut self,
inst: Inst,
ebb: Ebb,
constraints: &RecipeConstraints,
tracker: &mut LiveValueTracker,
) {
debug!("Inst {}, {}", self.cur.display_inst(inst), self.pressure); debug!("Inst {}, {}", self.cur.display_inst(inst), self.pressure);
debug_assert_eq!(self.cur.current_inst(), Some(inst)); debug_assert_eq!(self.cur.current_inst(), Some(inst));
debug_assert_eq!(self.cur.current_ebb(), Some(ebb)); debug_assert_eq!(self.cur.current_ebb(), Some(ebb));
let constraints = self
.encinfo
.operand_constraints(self.cur.func.encodings[inst]);
// We may need to resolve register constraints if there are any noteworthy uses. // We may need to resolve register constraints if there are any noteworthy uses.
debug_assert!(self.reg_uses.is_empty()); debug_assert!(self.reg_uses.is_empty());
self.collect_reg_uses(inst, ebb, constraints); self.collect_reg_uses(inst, ebb, constraints);
@@ -282,6 +277,7 @@ impl<'a> Context<'a> {
// Make sure we have enough registers for the register defs. // Make sure we have enough registers for the register defs.
// Dead defs are included here. They need a register too. // Dead defs are included here. They need a register too.
// No need to process call return values, they are in fixed registers. // No need to process call return values, they are in fixed registers.
if let Some(constraints) = constraints {
for op in constraints.outs { for op in constraints.outs {
if op.kind != ConstraintKind::Stack { if op.kind != ConstraintKind::Stack {
// Add register def to pressure, spill if needed. // Add register def to pressure, spill if needed.
@@ -299,6 +295,7 @@ impl<'a> Context<'a> {
} }
} }
self.pressure.reset_transient(); self.pressure.reset_transient();
}
// Restore pressure state, compute pressure with affinities from `defs`. // Restore pressure state, compute pressure with affinities from `defs`.
// Exclude dead defs. Includes call return values. // Exclude dead defs. Includes call return values.
@@ -315,8 +312,9 @@ impl<'a> Context<'a> {
// We are assuming here that if a value is used both by a fixed register operand and a register // We are assuming here that if a value is used both by a fixed register operand and a register
// class operand, they two are compatible. We are also assuming that two register class // class operand, they two are compatible. We are also assuming that two register class
// operands are always compatible. // operands are always compatible.
fn collect_reg_uses(&mut self, inst: Inst, ebb: Ebb, constraints: &RecipeConstraints) { fn collect_reg_uses(&mut self, inst: Inst, ebb: Ebb, constraints: Option<&RecipeConstraints>) {
let args = self.cur.func.dfg.inst_args(inst); let args = self.cur.func.dfg.inst_args(inst);
let num_fixed_ins = if let Some(constraints) = constraints {
for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() { for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() {
let mut reguse = RegUse::new(arg, idx, op.regclass.into()); let mut reguse = RegUse::new(arg, idx, op.regclass.into());
let lr = &self.liveness[arg]; let lr = &self.liveness[arg];
@@ -344,6 +342,12 @@ impl<'a> Context<'a> {
self.reg_uses.push(reguse); self.reg_uses.push(reguse);
} }
} }
constraints.ins.len()
} else {
// A non-ghost instruction with no constraints can't have any
// fixed operands.
0
};
// Similarly, for return instructions, collect uses of ABI-defined // Similarly, for return instructions, collect uses of ABI-defined
// return values. // return values.
@@ -356,7 +360,7 @@ impl<'a> Context<'a> {
for (ret_idx, (ret, &arg)) in for (ret_idx, (ret, &arg)) in
self.cur.func.signature.returns.iter().zip(args).enumerate() self.cur.func.signature.returns.iter().zip(args).enumerate()
{ {
let idx = constraints.ins.len() + ret_idx; let idx = num_fixed_ins + ret_idx;
let unit = match ret.location { let unit = match ret.location {
ArgumentLoc::Unassigned => { ArgumentLoc::Unassigned => {
panic!("function return signature should be legalized") panic!("function return signature should be legalized")