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:
committed by
Benjamin Bouvier
parent
ef2e11265c
commit
cd7c57e598
@@ -11,3 +11,13 @@ ebb0:
|
||||
; check: v2 = iconst.i64 0
|
||||
; check: v3 = copy v2
|
||||
; 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
|
||||
|
||||
@@ -134,11 +134,8 @@ impl<'a> Context<'a> {
|
||||
self.process_spills(tracker);
|
||||
|
||||
while let Some(inst) = self.cur.next_inst() {
|
||||
if let Some(constraints) = self
|
||||
.encinfo
|
||||
.operand_constraints(self.cur.func.encodings[inst])
|
||||
{
|
||||
self.visit_inst(inst, ebb, constraints, tracker);
|
||||
if !self.cur.func.dfg[inst].opcode().is_ghost() {
|
||||
self.visit_inst(inst, ebb, tracker);
|
||||
} else {
|
||||
let (_throughs, kills) = tracker.process_ghost(inst);
|
||||
self.free_regs(kills);
|
||||
@@ -237,17 +234,15 @@ impl<'a> Context<'a> {
|
||||
self.free_dead_regs(params);
|
||||
}
|
||||
|
||||
fn visit_inst(
|
||||
&mut self,
|
||||
inst: Inst,
|
||||
ebb: Ebb,
|
||||
constraints: &RecipeConstraints,
|
||||
tracker: &mut LiveValueTracker,
|
||||
) {
|
||||
fn visit_inst(&mut self, inst: Inst, ebb: Ebb, tracker: &mut LiveValueTracker) {
|
||||
debug!("Inst {}, {}", self.cur.display_inst(inst), self.pressure);
|
||||
debug_assert_eq!(self.cur.current_inst(), Some(inst));
|
||||
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.
|
||||
debug_assert!(self.reg_uses.is_empty());
|
||||
self.collect_reg_uses(inst, ebb, constraints);
|
||||
@@ -282,23 +277,25 @@ impl<'a> Context<'a> {
|
||||
// Make sure we have enough registers for the register defs.
|
||||
// Dead defs are included here. They need a register too.
|
||||
// No need to process call return values, they are in fixed registers.
|
||||
for op in constraints.outs {
|
||||
if op.kind != ConstraintKind::Stack {
|
||||
// Add register def to pressure, spill if needed.
|
||||
while let Err(mask) = self.pressure.take_transient(op.regclass) {
|
||||
debug!("Need {} reg from {} throughs", op.regclass, throughs.len());
|
||||
match self.spill_candidate(mask, throughs) {
|
||||
Some(cand) => self.spill_reg(cand),
|
||||
None => panic!(
|
||||
"Ran out of {} registers for {}",
|
||||
op.regclass,
|
||||
self.cur.display_inst(inst)
|
||||
),
|
||||
if let Some(constraints) = constraints {
|
||||
for op in constraints.outs {
|
||||
if op.kind != ConstraintKind::Stack {
|
||||
// Add register def to pressure, spill if needed.
|
||||
while let Err(mask) = self.pressure.take_transient(op.regclass) {
|
||||
debug!("Need {} reg from {} throughs", op.regclass, throughs.len());
|
||||
match self.spill_candidate(mask, throughs) {
|
||||
Some(cand) => self.spill_reg(cand),
|
||||
None => panic!(
|
||||
"Ran out of {} registers for {}",
|
||||
op.regclass,
|
||||
self.cur.display_inst(inst)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.pressure.reset_transient();
|
||||
}
|
||||
self.pressure.reset_transient();
|
||||
|
||||
// Restore pressure state, compute pressure with affinities from `defs`.
|
||||
// Exclude dead defs. Includes call return values.
|
||||
@@ -315,35 +312,42 @@ impl<'a> Context<'a> {
|
||||
// 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
|
||||
// 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);
|
||||
for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() {
|
||||
let mut reguse = RegUse::new(arg, idx, op.regclass.into());
|
||||
let lr = &self.liveness[arg];
|
||||
let ctx = self.liveness.context(&self.cur.func.layout);
|
||||
match op.kind {
|
||||
ConstraintKind::Stack => continue,
|
||||
ConstraintKind::FixedReg(_) => reguse.fixed = true,
|
||||
ConstraintKind::Tied(_) => {
|
||||
// A tied operand must kill the used value.
|
||||
reguse.tied = !lr.killed_at(inst, ebb, ctx);
|
||||
let num_fixed_ins = if let Some(constraints) = constraints {
|
||||
for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() {
|
||||
let mut reguse = RegUse::new(arg, idx, op.regclass.into());
|
||||
let lr = &self.liveness[arg];
|
||||
let ctx = self.liveness.context(&self.cur.func.layout);
|
||||
match op.kind {
|
||||
ConstraintKind::Stack => continue,
|
||||
ConstraintKind::FixedReg(_) => reguse.fixed = true,
|
||||
ConstraintKind::Tied(_) => {
|
||||
// A tied operand must kill the used value.
|
||||
reguse.tied = !lr.killed_at(inst, ebb, ctx);
|
||||
}
|
||||
ConstraintKind::FixedTied(_) => {
|
||||
reguse.fixed = true;
|
||||
reguse.tied = !lr.killed_at(inst, ebb, ctx);
|
||||
}
|
||||
ConstraintKind::Reg => {}
|
||||
}
|
||||
ConstraintKind::FixedTied(_) => {
|
||||
reguse.fixed = true;
|
||||
reguse.tied = !lr.killed_at(inst, ebb, ctx);
|
||||
if lr.affinity.is_stack() {
|
||||
reguse.spilled = true;
|
||||
}
|
||||
ConstraintKind::Reg => {}
|
||||
}
|
||||
if lr.affinity.is_stack() {
|
||||
reguse.spilled = true;
|
||||
}
|
||||
|
||||
// Only collect the interesting register uses.
|
||||
if reguse.fixed || reguse.tied || reguse.spilled {
|
||||
debug!(" reguse: {}", reguse);
|
||||
self.reg_uses.push(reguse);
|
||||
// Only collect the interesting register uses.
|
||||
if reguse.fixed || reguse.tied || reguse.spilled {
|
||||
debug!(" reguse: {}", 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
|
||||
// return values.
|
||||
@@ -356,7 +360,7 @@ impl<'a> Context<'a> {
|
||||
for (ret_idx, (ret, &arg)) in
|
||||
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 {
|
||||
ArgumentLoc::Unassigned => {
|
||||
panic!("function return signature should be legalized")
|
||||
|
||||
Reference in New Issue
Block a user