Avoid interference on CFG edges.

Track allocatable registers both locally and globally: Add a second
AllocatableSet which tracks registers allocated to global values without
accounting for register diversions. Since diversions are only local to
an EBB, global values must be assigned un-diverted locations that don't
interfere.

Handle the third "global" interference domain in the constraint solver in
addition to the existing "input" and "output" domains.

Extend the solver error code to indicate when a global define just can't
be allocated because there are not enough available global registers.
Resolve this problem by replacing the instruction's global defines with
local defines that are copied into their global destinations
afterwards.
This commit is contained in:
Jakob Stoklund Olesen
2017-10-10 09:45:06 -07:00
parent ba52a38597
commit 994af598f5
4 changed files with 378 additions and 90 deletions

View File

@@ -0,0 +1,27 @@
test compile
isa intel
; This test covers the troubles when values with global live ranges are defined
; by instructions with constrained register classes.
;
; The icmp_imm instrutions write their b1 result to the ABCD register class on
; 32-bit Intel. So if we define 5 live values, they can't all fit.
function %global_constraints(i32) {
ebb0(v0: i32):
v1 = icmp_imm eq v0, 1
v2 = icmp_imm ugt v0, 2
v3 = icmp_imm sle v0, 3
v4 = icmp_imm ne v0, 4
v5 = icmp_imm sge v0, 5
brnz v5, ebb1
return
ebb1:
; Make sure v1-v5 are live in.
v10 = band v1, v2
v11 = bor v3, v4
v12 = bor v10, v11
v13 = bor v12, v5
trapnz v13, user0
return
}

View File

@@ -45,7 +45,7 @@
use cursor::{Cursor, EncCursor};
use dominator_tree::DominatorTree;
use ir::{Ebb, Inst, Value, Function, ValueLoc, SigRef};
use ir::{InstBuilder, ArgumentType, ArgumentLoc};
use ir::{InstBuilder, ArgumentType, ArgumentLoc, ValueDef};
use isa::{RegUnit, RegClass, RegInfo, regs_overlap};
use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind};
use packed_option::PackedOption;
@@ -55,7 +55,8 @@ use regalloc::allocatable_set::AllocatableSet;
use regalloc::live_value_tracker::{LiveValue, LiveValueTracker};
use regalloc::liveness::Liveness;
use regalloc::liverange::LiveRange;
use regalloc::solver::Solver;
use regalloc::solver::{Solver, SolverError};
use std::mem;
/// Data structures for the coloring pass.
@@ -156,13 +157,16 @@ impl<'a> Context<'a> {
self.cur.goto_top(ebb);
while let Some(inst) = self.cur.next_inst() {
self.cur.use_srcloc(inst);
if let Some(constraints) =
self.encinfo.operand_constraints(
self.cur.func.encodings[inst],
)
{
self.visit_inst(inst, constraints, tracker, &mut regs);
let enc = self.cur.func.encodings[inst];
if let Some(constraints) = self.encinfo.operand_constraints(enc) {
if self.visit_inst(inst, constraints, tracker, &mut regs) {
self.replace_global_defines(inst, tracker);
// Restore cursor location after `replace_global_defines` moves it.
// We want to revisit the copy instructions it inserted.
self.cur.goto_inst(inst);
}
} else {
// This is a ghost instruction with no encoding.
let (_throughs, kills) = tracker.process_ghost(inst);
self.process_ghost_kills(kills, &mut regs);
}
@@ -173,7 +177,7 @@ impl<'a> Context<'a> {
/// Visit the `ebb` header.
///
/// Initialize the set of live registers and color the arguments to `ebb`.
fn visit_ebb_header(&mut self, ebb: Ebb, tracker: &mut LiveValueTracker) -> AllocatableSet {
fn visit_ebb_header(&mut self, ebb: Ebb, tracker: &mut LiveValueTracker) -> AvailableRegs {
// Reposition the live value tracker and deal with the EBB arguments.
tracker.ebb_top(
ebb,
@@ -198,31 +202,26 @@ impl<'a> Context<'a> {
///
/// Also process the EBB arguments which were colored when the first predecessor branch was
/// encountered.
fn livein_regs(&self, live: &[LiveValue]) -> AllocatableSet {
fn livein_regs(&self, live: &[LiveValue]) -> AvailableRegs {
// Start from the registers that are actually usable. We don't want to include any reserved
// registers in the set.
let mut regs = self.usable_regs.clone();
let mut regs = AvailableRegs::new(&self.usable_regs);
for lv in live.iter().filter(|lv| !lv.is_dead) {
let value = lv.value;
let affinity = self.liveness
.get(value)
.expect("No live range for live-in")
.affinity;
dbg!(
"Live-in: {}:{} in {}",
value,
affinity.display(&self.reginfo),
self.cur.func.locations[value].display(&self.reginfo)
lv.value,
lv.affinity.display(&self.reginfo),
self.cur.func.locations[lv.value].display(&self.reginfo)
);
if let Affinity::Reg(rci) = affinity {
if let Affinity::Reg(rci) = lv.affinity {
let rc = self.reginfo.rc(rci);
let loc = self.cur.func.locations[value];
let loc = self.cur.func.locations[lv.value];
match loc {
ValueLoc::Reg(reg) => regs.take(rc, reg),
ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", value),
ValueLoc::Reg(reg) => regs.take(rc, reg, lv.is_local),
ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", lv.value),
ValueLoc::Stack(ss) => {
panic!("Live-in {} is in {}, should be register", value, ss)
panic!("Live-in {} is in {}, should be register", lv.value, ss)
}
}
}
@@ -237,11 +236,11 @@ impl<'a> Context<'a> {
/// function signature.
///
/// Return the set of remaining allocatable registers after filtering out the dead arguments.
fn color_entry_args(&mut self, args: &[LiveValue]) -> AllocatableSet {
fn color_entry_args(&mut self, args: &[LiveValue]) -> AvailableRegs {
let sig = &self.cur.func.signature;
assert_eq!(sig.argument_types.len(), args.len());
let mut regs = self.usable_regs.clone();
let mut regs = AvailableRegs::new(&self.usable_regs);
for (lv, abi) in args.iter().zip(&sig.argument_types) {
match lv.affinity {
@@ -249,7 +248,7 @@ impl<'a> Context<'a> {
let rc = self.reginfo.rc(rci);
if let ArgumentLoc::Reg(reg) = abi.location {
if !lv.is_dead {
regs.take(rc, reg);
regs.take(rc, reg, lv.is_local);
}
self.cur.func.locations[lv.value] = ValueLoc::Reg(reg);
} else {
@@ -279,17 +278,19 @@ impl<'a> Context<'a> {
///
/// Update `regs` to reflect the allocated registers after `inst`, including removing any dead
/// or killed values from the set.
///
/// Returns true when the global values defined by `inst` must be replaced by local values.
fn visit_inst(
&mut self,
inst: Inst,
constraints: &RecipeConstraints,
tracker: &mut LiveValueTracker,
regs: &mut AllocatableSet,
) {
regs: &mut AvailableRegs,
) -> bool {
dbg!(
"Coloring {}\n from {}",
self.cur.display_inst(inst),
regs.display(&self.reginfo)
regs.input.display(&self.reginfo),
);
// EBB whose arguments should be colored to match the current branch instruction's
@@ -297,7 +298,7 @@ impl<'a> Context<'a> {
let mut color_dest_args = None;
// Program the solver with register constraints for the input side.
self.solver.reset(regs);
self.solver.reset(&regs.input);
self.program_input_constraints(inst, constraints.ins);
let call_sig = self.cur.func.dfg.call_signature(inst);
if let Some(sig) = call_sig {
@@ -352,14 +353,31 @@ impl<'a> Context<'a> {
// Get rid of the killed values.
for lv in kills {
if let Affinity::Reg(rci) = lv.affinity {
self.solver.add_kill(
let rc = self.reginfo.rc(rci);
let reg = self.divert.reg(lv.value, &self.cur.func.locations);
dbg!(
" kill {} in {} ({} {})",
lv.value,
self.reginfo.rc(rci),
self.divert.reg(lv.value, &self.cur.func.locations),
self.reginfo.display_regunit(reg),
if lv.is_local { "local" } else { "global" },
rc
);
self.solver.add_kill(lv.value, rc, reg);
// Update the global register set which has no diversions.
if !lv.is_local {
regs.global.free(
rc,
self.cur.func.locations[lv.value].unwrap_reg(),
);
}
}
}
// This aligns with the " from" line at the top of the function.
dbg!(" glob {}", regs.global.display(&self.reginfo));
// Program the fixed output constraints before the general defines. This allows us to
// detect conflicts between fixed outputs and tied operands where the input value hasn't
// been converted to a solver variable.
@@ -371,17 +389,22 @@ impl<'a> Context<'a> {
}
self.program_output_constraints(inst, constraints.outs, defs);
// This flag is set when the solver failed to find a solution for the global defines that
// doesn't interfere with `regs.global`. We need to rewrite all of `inst`s global defines
// as local defines followed by copies.
let mut replace_global_defines = false;
// Finally, we've fully programmed the constraint solver.
// We expect a quick solution in most cases.
let mut output_regs = self.solver.quick_solve().unwrap_or_else(|rc| {
dbg!("quick_solve needs more {} regs for {}", rc, self.solver);
self.iterate_solution(throughs)
let output_regs = self.solver.quick_solve(&regs.global).unwrap_or_else(|_| {
dbg!("quick_solve failed for {}", self.solver);
self.iterate_solution(throughs, &regs.global, &mut replace_global_defines)
});
// The solution and/or fixed input constraints may require us to shuffle the set of live
// registers around.
self.shuffle_inputs(regs);
self.shuffle_inputs(&mut regs.input);
// If this is the first time we branch to `dest`, color its arguments to match the current
// register state.
@@ -406,20 +429,42 @@ impl<'a> Context<'a> {
}
}
// Update `regs` for the next instruction, remove the dead defs.
// Update `regs` for the next instruction.
regs.input = output_regs;
for lv in defs {
if lv.endpoint == inst {
if let Affinity::Reg(rci) = lv.affinity {
let rc = self.reginfo.rc(rci);
let reg = self.divert.reg(lv.value, &self.cur.func.locations);
output_regs.free(rc, reg);
let loc = self.cur.func.locations[lv.value];
dbg!(
" color {} -> {}{}",
lv.value,
loc.display(&self.reginfo),
if lv.is_local {
""
} else if replace_global_defines {
" (global to be replaced)"
} else {
" (global)"
}
);
if let Affinity::Reg(rci) = lv.affinity {
let rc = self.reginfo.rc(rci);
// Remove the dead defs.
if lv.endpoint == inst {
regs.input.free(rc, loc.unwrap_reg());
debug_assert!(lv.is_local);
}
// Track globals in their undiverted locations.
if !lv.is_local && !replace_global_defines {
regs.global.take(rc, loc.unwrap_reg());
}
}
}
self.forget_diverted(kills);
*regs = output_regs;
replace_global_defines
}
/// Program the input-side constraints for `inst` into the constraint solver.
@@ -701,7 +746,7 @@ impl<'a> Context<'a> {
ConstraintKind::FixedReg(_) |
ConstraintKind::Stack => continue,
ConstraintKind::Reg => {
self.solver.add_def(lv.value, op.regclass);
self.solver.add_def(lv.value, op.regclass, !lv.is_local);
}
ConstraintKind::Tied(num) => {
// Find the input operand we're tied to.
@@ -711,6 +756,7 @@ impl<'a> Context<'a> {
arg,
op.regclass,
self.divert.reg(arg, &self.cur.func.locations),
!lv.is_local,
);
}
}
@@ -721,23 +767,35 @@ impl<'a> Context<'a> {
///
/// We may need to move more registers around before a solution is possible. Use an iterative
/// algorithm that adds one more variable until a solution can be found.
fn iterate_solution(&mut self, throughs: &[LiveValue]) -> AllocatableSet {
fn iterate_solution(
&mut self,
throughs: &[LiveValue],
global_regs: &AllocatableSet,
replace_global_defines: &mut bool,
) -> AllocatableSet {
// Make sure `try_add_var()` below doesn't create a variable with too loose constraints.
self.program_complete_input_constraints();
loop {
dbg!("real_solve for {}", self.solver);
let rc = match self.solver.real_solve() {
match self.solver.real_solve(global_regs) {
Ok(regs) => return regs,
Err(rc) => rc,
Err(SolverError::Divert(rc)) => {
// Do we have any live-through `rc` registers that are not already variables?
assert!(
self.try_add_var(rc, throughs),
"Ran out of registers in {}",
rc
);
}
Err(SolverError::Global(value)) => {
dbg!("Not enough global registers for {}, trying as local", value);
// We'll clear the `is_global` flag on all solver variables and instead make a
// note to replace all global defines with local defines followed by a copy.
*replace_global_defines = true;
self.solver.clear_all_global_flags();
}
};
// Do we have any live-through `rc` registers that are not already variables?
assert!(
self.try_add_var(rc, throughs),
"Ran out of registers in {}",
rc
);
}
}
@@ -749,7 +807,7 @@ impl<'a> Context<'a> {
if let Affinity::Reg(rci) = lv.affinity {
// The new variable gets to roam the whole top-level register class because it is
// not actually constrained by the instruction. We just want it out of the way.
let toprc2 = self.reginfo.rc(rci);
let toprc2 = self.reginfo.toprc(rci);
let reg2 = self.divert.reg(lv.value, &self.cur.func.locations);
if rc.contains(reg2) && self.solver.can_add_var(lv.value, toprc2, reg2) &&
!self.is_live_on_outgoing_edge(lv.value)
@@ -856,10 +914,82 @@ impl<'a> Context<'a> {
}
}
/// Replace all global values define by `inst` with local values that are then copied into the
/// global value:
///
/// v1 = foo
///
/// becomes:
///
/// v20 = foo
/// v1 = copy v20
///
/// This is sometimes necessary when there are no global registers available that can satisfy
/// the constraints on the instruction operands.
///
fn replace_global_defines(&mut self, inst: Inst, tracker: &mut LiveValueTracker) {
dbg!("Replacing global defs on {}", self.cur.display_inst(inst));
// We'll insert copies *after `inst`. Our caller will move the cursor back.
self.cur.next_inst();
// The tracker keeps the defs from `inst` at the end. Any dead defs have already been
// removed, so it's not obvious how many defs to process
for lv in tracker.live_mut().iter_mut().rev() {
// Keep going until we reach a value that is not defined by `inst`.
if match self.cur.func.dfg.value_def(lv.value) {
ValueDef::Res(i, _) => i != inst,
_ => true,
}
{
break;
}
if lv.is_local || !lv.affinity.is_reg() {
continue;
}
// Now `lv.value` is globally live and defined by `inst`. Replace it with a local live
// range that is copied after `inst`.
let ty = self.cur.func.dfg.value_type(lv.value);
let local = self.cur.func.dfg.replace_result(lv.value, ty);
self.cur.ins().with_result(lv.value).copy(local);
let copy = self.cur.built_inst();
// Create a live range for `local: inst -> copy`.
self.liveness.create_dead(local, inst, lv.affinity);
self.liveness.extend_locally(
local,
self.cur.func.layout.pp_ebb(inst),
copy,
&self.cur.func.layout,
);
// Move the definition of the global `lv.value`.
self.liveness.move_def_locally(lv.value, copy);
// Transfer the register coloring to `local`.
let loc = mem::replace(&mut self.cur.func.locations[lv.value], ValueLoc::default());
self.cur.func.locations[local] = loc;
// Update `lv` to reflect the new `local` live range.
lv.value = local;
lv.endpoint = copy;
lv.is_local = true;
dbg!(
" + {} with {} in {}",
self.cur.display_inst(copy),
local,
loc.display(&self.reginfo)
);
}
dbg!("Done: {}", self.cur.display_inst(inst));
}
/// Process kills on a ghost instruction.
/// - Forget diversions.
/// - Free killed registers.
fn process_ghost_kills(&mut self, kills: &[LiveValue], regs: &mut AllocatableSet) {
fn process_ghost_kills(&mut self, kills: &[LiveValue], regs: &mut AvailableRegs) {
for lv in kills {
if let Affinity::Reg(rci) = lv.affinity {
let rc = self.reginfo.rc(rci);
@@ -867,7 +997,13 @@ impl<'a> Context<'a> {
Some(loc) => loc,
None => self.cur.func.locations[lv.value],
};
regs.free(rc, loc.unwrap_reg());
regs.input.free(rc, loc.unwrap_reg());
if !lv.is_local {
regs.global.free(
rc,
self.cur.func.locations[lv.value].unwrap_reg(),
);
}
}
}
}
@@ -902,3 +1038,36 @@ fn program_input_abi(
}
}
}
/// Keep track of the set of available registers in two interference domains: all registers
/// considering diversions and global registers not considering diversions.
struct AvailableRegs {
/// The exact set of registers available on the input side of the current instruction. This
/// takes into account register diversions, and it includes both local and global live ranges.
input: AllocatableSet,
/// Registers available for allocating globally live values. This set ignores any local values,
/// and it does not account for register diversions.
///
/// Global values must be allocated out of this set because conflicts with other global values
/// can't be resolved with local diversions.
global: AllocatableSet,
}
impl AvailableRegs {
/// Initialize both the input and global sets from `regs`.
pub fn new(regs: &AllocatableSet) -> AvailableRegs {
AvailableRegs {
input: regs.clone(),
global: regs.clone(),
}
}
/// Take an un-diverted register from one or both sets.
pub fn take(&mut self, rc: RegClass, reg: RegUnit, is_local: bool) {
self.input.take(rc, reg);
if !is_local {
self.global.take(rc, reg);
}
}
}

View File

@@ -147,6 +147,13 @@ impl LiveValueTracker {
&self.live.values
}
/// Get a mutable set of currently live values.
///
/// Use with care and don't move entries around.
pub fn live_mut(&mut self) -> &mut [LiveValue] {
&mut self.live.values
}
/// Move the current position to the top of `ebb`.
///
/// This depends on the stored live value set at `ebb`'s immediate dominator, so that must have

View File

@@ -103,6 +103,7 @@ use entity::{SparseMap, SparseMapValue};
use ir::Value;
use isa::{RegClass, RegUnit};
use regalloc::allocatable_set::RegSetIter;
use std::cmp;
use std::fmt;
use std::mem;
use super::AllocatableSet;
@@ -161,14 +162,14 @@ impl Variable {
}
}
fn new_def(value: Value, constraint: RegClass) -> Variable {
fn new_def(value: Value, constraint: RegClass, is_global: bool) -> Variable {
Variable {
value,
constraint,
from: None,
is_input: false,
is_output: true,
is_global: false,
is_global,
domain: 0,
solution: !0,
}
@@ -180,17 +181,27 @@ impl Variable {
}
/// Get an iterator over possible register choices, given the available registers on the input
/// and output sides respectively.
fn iter(&self, iregs: &AllocatableSet, oregs: &AllocatableSet) -> RegSetIter {
if self.is_input && self.is_output {
let mut r = iregs.clone();
r.intersect(oregs);
r.iter(self.constraint)
} else if self.is_input {
iregs.iter(self.constraint)
} else {
oregs.iter(self.constraint)
/// and output sides as well as the available global register set.
fn iter(
&self,
iregs: &AllocatableSet,
oregs: &AllocatableSet,
gregs: &AllocatableSet,
) -> RegSetIter {
if !self.is_output {
debug_assert!(!self.is_global, "Global implies output");
debug_assert!(self.is_input, "Missing interference set");
return iregs.iter(self.constraint);
}
let mut r = oregs.clone();
if self.is_input {
r.intersect(iregs);
}
if self.is_global {
r.intersect(gregs);
}
r.iter(self.constraint)
}
}
@@ -736,7 +747,7 @@ impl Solver {
///
/// The output value that must have the same register as the input value is not recorded in the
/// solver.
pub fn add_tied_input(&mut self, value: Value, rc: RegClass, reg: RegUnit) {
pub fn add_tied_input(&mut self, value: Value, rc: RegClass, reg: RegUnit, is_global: bool) {
debug_assert!(self.inputs_done);
// If a fixed assignment is tied, the `to` register is not available on the output side.
@@ -750,10 +761,22 @@ impl Solver {
if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) {
assert!(v.is_input);
v.is_output = true;
v.is_global = is_global;
return;
}
self.regs_out.take(rc, reg);
// No variable exists for `value` because its constraints are already satisfied.
// However, if the tied output value has a global live range, we must create a variable to
// avoid global interference too.
if is_global {
let mut new_var = Variable::new_live(value, rc, reg, true);
new_var.is_global = true;
dbg!("add_tied_input: new tied-global var: {}", new_var);
self.vars.push(new_var);
self.regs_in.free(rc, reg);
} else {
self.regs_out.take(rc, reg);
}
}
/// Add a fixed output assignment.
@@ -778,10 +801,39 @@ impl Solver {
/// Add a defined output value.
///
/// This is similar to `add_var`, except the value doesn't have a prior register assignment.
pub fn add_def(&mut self, value: Value, constraint: RegClass) {
pub fn add_def(&mut self, value: Value, constraint: RegClass, is_global: bool) {
debug_assert!(self.inputs_done);
self.vars.push(Variable::new_def(value, constraint));
self.vars.push(
Variable::new_def(value, constraint, is_global),
);
}
/// Clear the `is_global` flag on all solver variables.
///
/// This is used when there are not enough global registers available, and global defines have
/// to be replaced with local defines followed by a copy.
pub fn clear_all_global_flags(&mut self) {
for v in &mut self.vars {
v.is_global = false;
}
}
}
/// Error reported when the solver fails to find a solution with the current constraints.
///
/// When no solution can be found, the error indicates how constraints could be loosened to help.
pub enum SolverError {
/// There are not available registers in the given register class.
///
/// This should be resolved by turning live-through values into variables so they can be moved
/// out of the way.
Divert(RegClass),
/// There are insufficient available registers in the global set to assign an `is_global`
/// variable with the given value.
///
/// This should be resolved by converting the variable to a local one.
Global(Value),
}
/// Interface for searching for a solution.
@@ -792,8 +844,11 @@ impl Solver {
/// always trivial.
///
/// Returns `Ok(regs)` if a solution was found.
pub fn quick_solve(&mut self) -> Result<AllocatableSet, RegClass> {
self.find_solution()
pub fn quick_solve(
&mut self,
global_regs: &AllocatableSet,
) -> Result<AllocatableSet, SolverError> {
self.find_solution(global_regs)
}
/// Try harder to find a solution.
@@ -803,9 +858,21 @@ impl Solver {
/// This may return an error with a register class that has run out of registers. If registers
/// can be freed up in the starving class, this method can be called again after adding
/// variables for the freed registers.
pub fn real_solve(&mut self) -> Result<AllocatableSet, RegClass> {
// TODO: Sort variables to assign smallest register classes first.
self.find_solution()
pub fn real_solve(
&mut self,
global_regs: &AllocatableSet,
) -> Result<AllocatableSet, SolverError> {
// Compute domain sizes for all the variables given the current register sets.
for v in &mut self.vars {
let d = v.iter(&self.regs_in, &self.regs_out, global_regs).len();
v.domain = cmp::min(d, u16::max_value() as usize) as u16;
}
// Solve for vars with small domains first to increase the chance of finding a solution.
// Use the value number as a tie breaker to get a stable sort.
self.vars.sort_unstable_by_key(|v| (v.domain, v.value));
dbg!("real_solve for {}", self);
self.find_solution(global_regs)
}
/// Search for a solution with the current list of variables.
@@ -813,16 +880,28 @@ impl Solver {
/// If a solution was found, returns `Ok(regs)` with the set of available registers on the
/// output side after the solution. If no solution could be found, returns `Err(rc)` with the
/// constraint register class that needs more available registers.
fn find_solution(&mut self) -> Result<AllocatableSet, RegClass> {
fn find_solution(
&mut self,
global_regs: &AllocatableSet,
) -> Result<AllocatableSet, SolverError> {
// Available registers on the input and output sides respectively.
let mut iregs = self.regs_in.clone();
let mut oregs = self.regs_out.clone();
let mut gregs = global_regs.clone();
for v in &mut self.vars {
let rc = v.constraint;
let reg = match v.iter(&iregs, &oregs).next() {
None => return Err(rc),
let reg = match v.iter(&iregs, &oregs, &gregs).next() {
Some(reg) => reg,
None => {
// If `v` must avoid global interference, there is not point in requesting
// live registers be diverted. We need to make it a non-global variable.
if v.is_global && gregs.iter(rc).next().is_none() {
return Err(SolverError::Global(v.value));
} else {
return Err(SolverError::Divert(rc));
}
}
};
v.solution = reg;
@@ -832,6 +911,9 @@ impl Solver {
if v.is_output {
oregs.take(rc, reg);
}
if v.is_global {
gregs.take(rc, reg);
}
}
Ok(oregs)
@@ -1083,6 +1165,7 @@ mod tests {
let r0 = gpr.unit(0);
let r1 = gpr.unit(1);
let r2 = gpr.unit(2);
let gregs = AllocatableSet::new();
let mut regs = AllocatableSet::new();
let mut solver = Solver::new();
let v10 = Value::new(10);
@@ -1093,7 +1176,7 @@ mod tests {
solver.reset(&regs);
solver.reassign_in(v10, gpr, r1, r0);
solver.inputs_done();
assert!(solver.quick_solve().is_ok());
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(solver.moves(), &[mov(v10, gpr, r1, r0)]);
@@ -1103,7 +1186,7 @@ mod tests {
solver.reassign_in(v10, gpr, r0, r1);
solver.reassign_in(v11, gpr, r1, r2);
solver.inputs_done();
assert!(solver.quick_solve().is_ok());
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
@@ -1115,7 +1198,7 @@ mod tests {
solver.reassign_in(v10, gpr, r0, r1);
solver.reassign_in(v11, gpr, r1, r0);
solver.inputs_done();
assert!(solver.quick_solve().is_ok());
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
@@ -1140,6 +1223,7 @@ mod tests {
let s1 = s.unit(1);
let s2 = s.unit(2);
let s3 = s.unit(3);
let gregs = AllocatableSet::new();
let mut regs = AllocatableSet::new();
let mut solver = Solver::new();
let v10 = Value::new(10);
@@ -1154,7 +1238,7 @@ mod tests {
solver.reassign_in(v11, s, s2, s0);
solver.reassign_in(v12, s, s3, s1);
solver.inputs_done();
assert!(solver.quick_solve().is_ok());
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
@@ -1175,7 +1259,7 @@ mod tests {
solver.reassign_in(v12, s, s1, s3);
solver.reassign_in(v10, d, d1, d0);
solver.inputs_done();
assert!(solver.quick_solve().is_ok());
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 0);
assert_eq!(
solver.moves(),
@@ -1199,6 +1283,7 @@ mod tests {
let r3 = gpr.unit(3);
let r4 = gpr.unit(4);
let r5 = gpr.unit(5);
let gregs = AllocatableSet::new();
let mut regs = AllocatableSet::new();
let mut solver = Solver::new();
let v10 = Value::new(10);
@@ -1219,7 +1304,7 @@ mod tests {
solver.reassign_in(v11, gpr, r1, r2);
solver.reassign_in(v12, gpr, r2, r0);
solver.inputs_done();
assert!(solver.quick_solve().is_ok());
assert!(solver.quick_solve(&gregs).is_ok());
assert_eq!(solver.schedule_moves(&regs), 1);
assert_eq!(
solver.moves(),
@@ -1243,7 +1328,7 @@ mod tests {
solver.reassign_in(v15, gpr, r5, r3);
solver.inputs_done();
assert!(solver.quick_solve().is_ok());
assert!(solver.quick_solve(&gregs).is_ok());
// We resolve two cycles with one spill.
assert_eq!(solver.schedule_moves(&regs), 1);
assert_eq!(