Repair constraint violations during spilling.

The following constraints may need to be resolved during spilling
because the resolution increases register pressure:

- A tied operand whose value is live through the instruction.
- A fixed register constraint for a value used more than once.
- A register use of a spilled value needs to account for the reload
  register.
This commit is contained in:
Jakob Stoklund Olesen
2017-06-29 16:51:05 -07:00
parent 51dcabd87c
commit bf5281ca41
2 changed files with 147 additions and 55 deletions

View File

@@ -144,3 +144,53 @@ ebb1(v10: i32, v11: i32, v12: i32, v13: i32, v14: i32, v15: i32, v16: i32, v17:
v33 = iadd v32, v1
return v33
}
; In straight-line code, the first value defined is spilled.
; That is in order:
; 1. The argument v1.
; 2. The link register.
; 3. The first computed value, v2
function %use_spilled_value(i32) -> i32 {
; check: ss0 = spill_slot 4
; check: ss1 = spill_slot 4
; check: ss2 = spill_slot 4
ebb0(v1: i32):
; check: $ebb0($(rv1=$V): i32, $(rlink=$V): i32)
; check: ,ss0]$WS $v1 = spill $rv1
; nextln: ,ss1]$WS $(link=$V) = spill $rlink
; not: spill
v2 = iadd_imm v1, 12
; check: $(r1v2=$V) = iadd_imm
; nextln: ,ss2]$WS $v2 = spill $r1v2
v3 = iadd_imm v2, 12
v4 = iadd_imm v3, 12
v5 = iadd_imm v4, 12
v6 = iadd_imm v5, 12
v7 = iadd_imm v6, 12
v8 = iadd_imm v7, 12
v9 = iadd_imm v8, 12
v10 = iadd_imm v9, 12
v11 = iadd_imm v10, 12
v12 = iadd_imm v11, 12
v13 = iadd_imm v12, 12
v14 = iadd_imm v13, 12
; Here we have maximum register pressure, and v2 has been spilled.
; What happens if we use it?
v33 = iadd v2, v14
v32 = iadd v33, v12
v31 = iadd v32, v11
v30 = iadd v31, v10
v29 = iadd v30, v9
v28 = iadd v29, v8
v27 = iadd v28, v7
v26 = iadd v27, v6
v25 = iadd v26, v5
v24 = iadd v25, v4
v23 = iadd v24, v3
v22 = iadd v23, v2
v21 = iadd v22, v1
v20 = iadd v21, v13
v19 = iadd v20, v2
return v21
}

View File

@@ -19,7 +19,7 @@ use dominator_tree::DominatorTree;
use ir::{DataFlowGraph, Layout, Cursor, InstBuilder};
use ir::{Function, Ebb, Inst, Value, ValueLoc, ArgumentLoc, Signature, SigRef};
use ir::{InstEncodings, StackSlots, ValueLocations};
use isa::registers::{RegClass, RegClassMask};
use isa::registers::{RegClassMask, RegClassIndex};
use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind};
use regalloc::affinity::Affinity;
use regalloc::live_value_tracker::{LiveValue, LiveValueTracker};
@@ -245,18 +245,10 @@ impl<'a> Context<'a> {
dfg: &mut DataFlowGraph,
tracker: &mut LiveValueTracker) {
dbg!("Inst {}, {}", dfg.display_inst(inst), self.pressure);
// TODO: Repair constraint violations by copying input values.
//
// - Tied use of value that is not killed.
// - Count pressure for register uses of spilled values too.
// We may need to resolve register constraints if there are any noteworthy uses.
assert!(self.reg_uses.is_empty());
// If the instruction has any fixed register operands, we may need to resolve register
// constraints.
if constraints.fixed_ins {
self.collect_reg_uses(inst, constraints, dfg);
}
self.collect_reg_uses(inst, constraints, dfg);
// Calls usually have fixed register uses.
let call_sig = dfg.call_signature(inst);
@@ -268,7 +260,6 @@ impl<'a> Context<'a> {
self.process_reg_uses(inst, pos, dfg, tracker);
}
// Update the live value tracker with this instruction.
let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness);
@@ -312,7 +303,12 @@ impl<'a> Context<'a> {
// This won't cause spilling.
self.take_live_regs(defs);
}
// Collect register uses from the fixed input constraints.
// Collect register uses that are noteworthy in one of the following ways:
//
// 1. It's a fixed register constraint.
// 2. It's a use of a spilled value.
// 3. It's a tied register constraint and the value isn't killed.
//
// 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
@@ -323,11 +319,23 @@ impl<'a> Context<'a> {
dfg: &DataFlowGraph) {
let args = 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());
match op.kind {
ConstraintKind::FixedReg(_) => {
self.reg_uses.push(RegUse::new(arg, idx));
ConstraintKind::Stack => continue,
ConstraintKind::FixedReg(_) => reguse.fixed = true,
ConstraintKind::Tied(_) => {
// TODO: If `arg` isn't killed here, we need a copy
}
_ => {}
ConstraintKind::Reg => {}
}
let lr = &self.liveness[arg];
if lr.affinity.is_stack() {
reguse.spilled = true;
}
// Only collect the interesting register uses.
if reguse.fixed || reguse.spilled {
self.reg_uses.push(reguse);
}
}
}
@@ -343,7 +351,17 @@ impl<'a> Context<'a> {
.zip(args)
.enumerate() {
if abi.location.is_reg() {
self.reg_uses.push(RegUse::new(arg, fixed_args + idx));
let (rci, spilled) = match self.liveness[arg].affinity {
Affinity::Reg(rci) => (rci, false),
Affinity::Stack => {
(self.isa.regclass_for_abi_type(abi.value_type).into(), true)
}
Affinity::None => panic!("Missing affinity for {}", arg),
};
let mut reguse = RegUse::new(arg, fixed_args + idx, rci);
reguse.fixed = true;
reguse.spilled = spilled;
self.reg_uses.push(reguse);
}
}
}
@@ -364,35 +382,49 @@ impl<'a> Context<'a> {
// outside nightly Rust.
self.reg_uses.sort_by_key(|u| (u.value, u.opidx));
// We are assuming that `reg_uses` has an entry per fixed register operand, and that any
// non-fixed register operands are compatible with one of the fixed uses of the value.
for i in 1..self.reg_uses.len() {
for i in 0..self.reg_uses.len() {
let ru = self.reg_uses[i];
if self.reg_uses[i - 1].value != ru.value {
continue;
// Do we need to insert a copy for this use?
let need_copy = if ru.tied {
true
} else if ru.fixed {
// This is a fixed register use which doesn't necessarily require a copy.
// Make a copy only if this is not the first use of the value.
self.reg_uses
.get(i.wrapping_sub(1))
.map(|ru2| ru2.value == ru.value)
.unwrap_or(false)
} else {
false
};
if need_copy {
let copy = self.insert_copy(ru.value, ru.rci, pos, dfg);
dfg.inst_args_mut(inst)[ru.opidx as usize] = copy;
}
// We have two fixed uses of the same value. Make a copy.
let (copy, rc) = self.insert_copy(ru.value, pos, dfg);
dfg.inst_args_mut(inst)[ru.opidx as usize] = copy;
// Make sure the new copy doesn't blow the register pressure.
while let Err(mask) = self.pressure.take_transient(rc) {
dbg!("Copy of {} reg causes spill", rc);
// Spill a live register that is *not* used by the current instruction.
// Spilling a use wouldn't help.
let args = dfg.inst_args(inst);
match self.spill_candidate(mask,
tracker.live().iter().filter(|lv| {
!args.contains(&lv.value)
}),
dfg,
&pos.layout) {
Some(cand) => self.spill_reg(cand, dfg),
None => {
panic!("Ran out of {} registers when inserting copy before {}",
rc,
dfg.display_inst(inst))
// Even if we don't insert a copy, we may need to account for register pressure for the
// reload pass.
if need_copy || ru.spilled {
let rc = self.reginfo.rc(ru.rci);
while let Err(mask) = self.pressure.take_transient(rc) {
dbg!("Copy of {} reg causes spill", rc);
// Spill a live register that is *not* used by the current instruction.
// Spilling a use wouldn't help.
let args = dfg.inst_args(inst);
match self.spill_candidate(mask,
tracker.live().iter().filter(|lv| {
!args.contains(&lv.value)
}),
dfg,
&pos.layout) {
Some(cand) => self.spill_reg(cand, dfg),
None => {
panic!("Ran out of {} registers when inserting copy before {}",
rc,
dfg.display_inst(inst))
}
}
}
}
@@ -481,12 +513,13 @@ impl<'a> Context<'a> {
/// Insert a `copy value` before `pos` and give it a live range extending to `pos`.
///
/// Returns the new local value created and its register class.
/// Returns the new local value created.
fn insert_copy(&mut self,
value: Value,
rci: RegClassIndex,
pos: &mut Cursor,
dfg: &mut DataFlowGraph)
-> (Value, RegClass) {
-> Value {
let copy = dfg.ins(pos).copy(value);
let inst = dfg.value_def(copy).unwrap_inst();
let ty = dfg.value_type(copy);
@@ -498,21 +531,14 @@ impl<'a> Context<'a> {
*self.encodings.ensure(inst) = encoding;
// Update live ranges.
let rc = self.encinfo
.operand_constraints(encoding)
.expect("Bad copy encoding")
.outs
[0]
.regclass;
self.liveness
.create_dead(copy, inst, Affinity::Reg(rc.into()));
self.liveness.create_dead(copy, inst, Affinity::Reg(rci));
self.liveness
.extend_locally(copy,
pos.layout.pp_ebb(inst),
pos.current_inst().expect("must be at an instruction"),
pos.layout);
(copy, rc)
copy
}
}
@@ -522,13 +548,29 @@ impl<'a> Context<'a> {
struct RegUse {
value: Value,
opidx: u16,
// Register class required by the use.
rci: RegClassIndex,
// A use with a fixed register constraint.
fixed: bool,
// A register use of a spilled value.
spilled: bool,
// A use with a tied register constraint *and* the used value is not killed.
tied: bool,
}
impl RegUse {
fn new(value: Value, idx: usize) -> RegUse {
fn new(value: Value, idx: usize, rci: RegClassIndex) -> RegUse {
RegUse {
value,
opidx: idx as u16,
rci,
fixed: false,
spilled: false,
tied: false,
}
}
}