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:
27
cranelift/filetests/regalloc/global-constraints.cton
Normal file
27
cranelift/filetests/regalloc/global-constraints.cton
Normal 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
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
use cursor::{Cursor, EncCursor};
|
use cursor::{Cursor, EncCursor};
|
||||||
use dominator_tree::DominatorTree;
|
use dominator_tree::DominatorTree;
|
||||||
use ir::{Ebb, Inst, Value, Function, ValueLoc, SigRef};
|
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::{RegUnit, RegClass, RegInfo, regs_overlap};
|
||||||
use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind};
|
use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind};
|
||||||
use packed_option::PackedOption;
|
use packed_option::PackedOption;
|
||||||
@@ -55,7 +55,8 @@ use regalloc::allocatable_set::AllocatableSet;
|
|||||||
use regalloc::live_value_tracker::{LiveValue, LiveValueTracker};
|
use regalloc::live_value_tracker::{LiveValue, LiveValueTracker};
|
||||||
use regalloc::liveness::Liveness;
|
use regalloc::liveness::Liveness;
|
||||||
use regalloc::liverange::LiveRange;
|
use regalloc::liverange::LiveRange;
|
||||||
use regalloc::solver::Solver;
|
use regalloc::solver::{Solver, SolverError};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
|
||||||
/// Data structures for the coloring pass.
|
/// Data structures for the coloring pass.
|
||||||
@@ -156,13 +157,16 @@ impl<'a> Context<'a> {
|
|||||||
self.cur.goto_top(ebb);
|
self.cur.goto_top(ebb);
|
||||||
while let Some(inst) = self.cur.next_inst() {
|
while let Some(inst) = self.cur.next_inst() {
|
||||||
self.cur.use_srcloc(inst);
|
self.cur.use_srcloc(inst);
|
||||||
if let Some(constraints) =
|
let enc = self.cur.func.encodings[inst];
|
||||||
self.encinfo.operand_constraints(
|
if let Some(constraints) = self.encinfo.operand_constraints(enc) {
|
||||||
self.cur.func.encodings[inst],
|
if self.visit_inst(inst, constraints, tracker, &mut regs) {
|
||||||
)
|
self.replace_global_defines(inst, tracker);
|
||||||
{
|
// Restore cursor location after `replace_global_defines` moves it.
|
||||||
self.visit_inst(inst, constraints, tracker, &mut regs);
|
// We want to revisit the copy instructions it inserted.
|
||||||
|
self.cur.goto_inst(inst);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// This is a ghost instruction with no encoding.
|
||||||
let (_throughs, kills) = tracker.process_ghost(inst);
|
let (_throughs, kills) = tracker.process_ghost(inst);
|
||||||
self.process_ghost_kills(kills, &mut regs);
|
self.process_ghost_kills(kills, &mut regs);
|
||||||
}
|
}
|
||||||
@@ -173,7 +177,7 @@ impl<'a> Context<'a> {
|
|||||||
/// Visit the `ebb` header.
|
/// Visit the `ebb` header.
|
||||||
///
|
///
|
||||||
/// Initialize the set of live registers and color the arguments to `ebb`.
|
/// 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.
|
// Reposition the live value tracker and deal with the EBB arguments.
|
||||||
tracker.ebb_top(
|
tracker.ebb_top(
|
||||||
ebb,
|
ebb,
|
||||||
@@ -198,31 +202,26 @@ impl<'a> Context<'a> {
|
|||||||
///
|
///
|
||||||
/// Also process the EBB arguments which were colored when the first predecessor branch was
|
/// Also process the EBB arguments which were colored when the first predecessor branch was
|
||||||
/// encountered.
|
/// 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
|
// Start from the registers that are actually usable. We don't want to include any reserved
|
||||||
// registers in the set.
|
// 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) {
|
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!(
|
dbg!(
|
||||||
"Live-in: {}:{} in {}",
|
"Live-in: {}:{} in {}",
|
||||||
value,
|
lv.value,
|
||||||
affinity.display(&self.reginfo),
|
lv.affinity.display(&self.reginfo),
|
||||||
self.cur.func.locations[value].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 rc = self.reginfo.rc(rci);
|
||||||
let loc = self.cur.func.locations[value];
|
let loc = self.cur.func.locations[lv.value];
|
||||||
match loc {
|
match loc {
|
||||||
ValueLoc::Reg(reg) => regs.take(rc, reg),
|
ValueLoc::Reg(reg) => regs.take(rc, reg, lv.is_local),
|
||||||
ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", value),
|
ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", lv.value),
|
||||||
ValueLoc::Stack(ss) => {
|
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.
|
/// function signature.
|
||||||
///
|
///
|
||||||
/// Return the set of remaining allocatable registers after filtering out the dead arguments.
|
/// 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;
|
let sig = &self.cur.func.signature;
|
||||||
assert_eq!(sig.argument_types.len(), args.len());
|
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) {
|
for (lv, abi) in args.iter().zip(&sig.argument_types) {
|
||||||
match lv.affinity {
|
match lv.affinity {
|
||||||
@@ -249,7 +248,7 @@ impl<'a> Context<'a> {
|
|||||||
let rc = self.reginfo.rc(rci);
|
let rc = self.reginfo.rc(rci);
|
||||||
if let ArgumentLoc::Reg(reg) = abi.location {
|
if let ArgumentLoc::Reg(reg) = abi.location {
|
||||||
if !lv.is_dead {
|
if !lv.is_dead {
|
||||||
regs.take(rc, reg);
|
regs.take(rc, reg, lv.is_local);
|
||||||
}
|
}
|
||||||
self.cur.func.locations[lv.value] = ValueLoc::Reg(reg);
|
self.cur.func.locations[lv.value] = ValueLoc::Reg(reg);
|
||||||
} else {
|
} else {
|
||||||
@@ -279,17 +278,19 @@ impl<'a> Context<'a> {
|
|||||||
///
|
///
|
||||||
/// Update `regs` to reflect the allocated registers after `inst`, including removing any dead
|
/// Update `regs` to reflect the allocated registers after `inst`, including removing any dead
|
||||||
/// or killed values from the set.
|
/// or killed values from the set.
|
||||||
|
///
|
||||||
|
/// Returns true when the global values defined by `inst` must be replaced by local values.
|
||||||
fn visit_inst(
|
fn visit_inst(
|
||||||
&mut self,
|
&mut self,
|
||||||
inst: Inst,
|
inst: Inst,
|
||||||
constraints: &RecipeConstraints,
|
constraints: &RecipeConstraints,
|
||||||
tracker: &mut LiveValueTracker,
|
tracker: &mut LiveValueTracker,
|
||||||
regs: &mut AllocatableSet,
|
regs: &mut AvailableRegs,
|
||||||
) {
|
) -> bool {
|
||||||
dbg!(
|
dbg!(
|
||||||
"Coloring {}\n from {}",
|
"Coloring {}\n from {}",
|
||||||
self.cur.display_inst(inst),
|
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
|
// 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;
|
let mut color_dest_args = None;
|
||||||
|
|
||||||
// Program the solver with register constraints for the input side.
|
// Program the solver with register constraints for the input side.
|
||||||
self.solver.reset(regs);
|
self.solver.reset(®s.input);
|
||||||
self.program_input_constraints(inst, constraints.ins);
|
self.program_input_constraints(inst, constraints.ins);
|
||||||
let call_sig = self.cur.func.dfg.call_signature(inst);
|
let call_sig = self.cur.func.dfg.call_signature(inst);
|
||||||
if let Some(sig) = call_sig {
|
if let Some(sig) = call_sig {
|
||||||
@@ -352,14 +353,31 @@ impl<'a> Context<'a> {
|
|||||||
// Get rid of the killed values.
|
// Get rid of the killed values.
|
||||||
for lv in kills {
|
for lv in kills {
|
||||||
if let Affinity::Reg(rci) = lv.affinity {
|
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,
|
lv.value,
|
||||||
self.reginfo.rc(rci),
|
self.reginfo.display_regunit(reg),
|
||||||
self.divert.reg(lv.value, &self.cur.func.locations),
|
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
|
// 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
|
// detect conflicts between fixed outputs and tied operands where the input value hasn't
|
||||||
// been converted to a solver variable.
|
// been converted to a solver variable.
|
||||||
@@ -371,17 +389,22 @@ impl<'a> Context<'a> {
|
|||||||
}
|
}
|
||||||
self.program_output_constraints(inst, constraints.outs, defs);
|
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.
|
// Finally, we've fully programmed the constraint solver.
|
||||||
// We expect a quick solution in most cases.
|
// We expect a quick solution in most cases.
|
||||||
let mut output_regs = self.solver.quick_solve().unwrap_or_else(|rc| {
|
let output_regs = self.solver.quick_solve(®s.global).unwrap_or_else(|_| {
|
||||||
dbg!("quick_solve needs more {} regs for {}", rc, self.solver);
|
dbg!("quick_solve failed for {}", self.solver);
|
||||||
self.iterate_solution(throughs)
|
self.iterate_solution(throughs, ®s.global, &mut replace_global_defines)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// The solution and/or fixed input constraints may require us to shuffle the set of live
|
// The solution and/or fixed input constraints may require us to shuffle the set of live
|
||||||
// registers around.
|
// 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
|
// If this is the first time we branch to `dest`, color its arguments to match the current
|
||||||
// register state.
|
// 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 {
|
for lv in defs {
|
||||||
if lv.endpoint == inst {
|
let loc = self.cur.func.locations[lv.value];
|
||||||
if let Affinity::Reg(rci) = lv.affinity {
|
dbg!(
|
||||||
let rc = self.reginfo.rc(rci);
|
" color {} -> {}{}",
|
||||||
let reg = self.divert.reg(lv.value, &self.cur.func.locations);
|
lv.value,
|
||||||
output_regs.free(rc, reg);
|
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);
|
self.forget_diverted(kills);
|
||||||
|
|
||||||
*regs = output_regs;
|
replace_global_defines
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Program the input-side constraints for `inst` into the constraint solver.
|
/// Program the input-side constraints for `inst` into the constraint solver.
|
||||||
@@ -701,7 +746,7 @@ impl<'a> Context<'a> {
|
|||||||
ConstraintKind::FixedReg(_) |
|
ConstraintKind::FixedReg(_) |
|
||||||
ConstraintKind::Stack => continue,
|
ConstraintKind::Stack => continue,
|
||||||
ConstraintKind::Reg => {
|
ConstraintKind::Reg => {
|
||||||
self.solver.add_def(lv.value, op.regclass);
|
self.solver.add_def(lv.value, op.regclass, !lv.is_local);
|
||||||
}
|
}
|
||||||
ConstraintKind::Tied(num) => {
|
ConstraintKind::Tied(num) => {
|
||||||
// Find the input operand we're tied to.
|
// Find the input operand we're tied to.
|
||||||
@@ -711,6 +756,7 @@ impl<'a> Context<'a> {
|
|||||||
arg,
|
arg,
|
||||||
op.regclass,
|
op.regclass,
|
||||||
self.divert.reg(arg, &self.cur.func.locations),
|
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
|
/// 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.
|
/// 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.
|
// Make sure `try_add_var()` below doesn't create a variable with too loose constraints.
|
||||||
self.program_complete_input_constraints();
|
self.program_complete_input_constraints();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
dbg!("real_solve for {}", self.solver);
|
match self.solver.real_solve(global_regs) {
|
||||||
let rc = match self.solver.real_solve() {
|
|
||||||
Ok(regs) => return 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 {
|
if let Affinity::Reg(rci) = lv.affinity {
|
||||||
// The new variable gets to roam the whole top-level register class because it is
|
// 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.
|
// 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);
|
let reg2 = self.divert.reg(lv.value, &self.cur.func.locations);
|
||||||
if rc.contains(reg2) && self.solver.can_add_var(lv.value, toprc2, reg2) &&
|
if rc.contains(reg2) && self.solver.can_add_var(lv.value, toprc2, reg2) &&
|
||||||
!self.is_live_on_outgoing_edge(lv.value)
|
!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.
|
/// Process kills on a ghost instruction.
|
||||||
/// - Forget diversions.
|
/// - Forget diversions.
|
||||||
/// - Free killed registers.
|
/// - 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 {
|
for lv in kills {
|
||||||
if let Affinity::Reg(rci) = lv.affinity {
|
if let Affinity::Reg(rci) = lv.affinity {
|
||||||
let rc = self.reginfo.rc(rci);
|
let rc = self.reginfo.rc(rci);
|
||||||
@@ -867,7 +997,13 @@ impl<'a> Context<'a> {
|
|||||||
Some(loc) => loc,
|
Some(loc) => loc,
|
||||||
None => self.cur.func.locations[lv.value],
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,6 +147,13 @@ impl LiveValueTracker {
|
|||||||
&self.live.values
|
&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`.
|
/// 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
|
/// This depends on the stored live value set at `ebb`'s immediate dominator, so that must have
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ use entity::{SparseMap, SparseMapValue};
|
|||||||
use ir::Value;
|
use ir::Value;
|
||||||
use isa::{RegClass, RegUnit};
|
use isa::{RegClass, RegUnit};
|
||||||
use regalloc::allocatable_set::RegSetIter;
|
use regalloc::allocatable_set::RegSetIter;
|
||||||
|
use std::cmp;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use super::AllocatableSet;
|
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 {
|
Variable {
|
||||||
value,
|
value,
|
||||||
constraint,
|
constraint,
|
||||||
from: None,
|
from: None,
|
||||||
is_input: false,
|
is_input: false,
|
||||||
is_output: true,
|
is_output: true,
|
||||||
is_global: false,
|
is_global,
|
||||||
domain: 0,
|
domain: 0,
|
||||||
solution: !0,
|
solution: !0,
|
||||||
}
|
}
|
||||||
@@ -180,17 +181,27 @@ impl Variable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator over possible register choices, given the available registers on the input
|
/// Get an iterator over possible register choices, given the available registers on the input
|
||||||
/// and output sides respectively.
|
/// and output sides as well as the available global register set.
|
||||||
fn iter(&self, iregs: &AllocatableSet, oregs: &AllocatableSet) -> RegSetIter {
|
fn iter(
|
||||||
if self.is_input && self.is_output {
|
&self,
|
||||||
let mut r = iregs.clone();
|
iregs: &AllocatableSet,
|
||||||
r.intersect(oregs);
|
oregs: &AllocatableSet,
|
||||||
r.iter(self.constraint)
|
gregs: &AllocatableSet,
|
||||||
} else if self.is_input {
|
) -> RegSetIter {
|
||||||
iregs.iter(self.constraint)
|
if !self.is_output {
|
||||||
} else {
|
debug_assert!(!self.is_global, "Global implies output");
|
||||||
oregs.iter(self.constraint)
|
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
|
/// The output value that must have the same register as the input value is not recorded in the
|
||||||
/// solver.
|
/// 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);
|
debug_assert!(self.inputs_done);
|
||||||
|
|
||||||
// If a fixed assignment is tied, the `to` register is not available on the output side.
|
// 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) {
|
if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) {
|
||||||
assert!(v.is_input);
|
assert!(v.is_input);
|
||||||
v.is_output = true;
|
v.is_output = true;
|
||||||
|
v.is_global = is_global;
|
||||||
return;
|
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.
|
/// Add a fixed output assignment.
|
||||||
@@ -778,10 +801,39 @@ impl Solver {
|
|||||||
/// Add a defined output value.
|
/// Add a defined output value.
|
||||||
///
|
///
|
||||||
/// This is similar to `add_var`, except the value doesn't have a prior register assignment.
|
/// 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);
|
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.
|
/// Interface for searching for a solution.
|
||||||
@@ -792,8 +844,11 @@ impl Solver {
|
|||||||
/// always trivial.
|
/// always trivial.
|
||||||
///
|
///
|
||||||
/// Returns `Ok(regs)` if a solution was found.
|
/// Returns `Ok(regs)` if a solution was found.
|
||||||
pub fn quick_solve(&mut self) -> Result<AllocatableSet, RegClass> {
|
pub fn quick_solve(
|
||||||
self.find_solution()
|
&mut self,
|
||||||
|
global_regs: &AllocatableSet,
|
||||||
|
) -> Result<AllocatableSet, SolverError> {
|
||||||
|
self.find_solution(global_regs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try harder to find a solution.
|
/// 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
|
/// 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
|
/// can be freed up in the starving class, this method can be called again after adding
|
||||||
/// variables for the freed registers.
|
/// variables for the freed registers.
|
||||||
pub fn real_solve(&mut self) -> Result<AllocatableSet, RegClass> {
|
pub fn real_solve(
|
||||||
// TODO: Sort variables to assign smallest register classes first.
|
&mut self,
|
||||||
self.find_solution()
|
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.
|
/// 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
|
/// 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
|
/// output side after the solution. If no solution could be found, returns `Err(rc)` with the
|
||||||
/// constraint register class that needs more available registers.
|
/// 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.
|
// Available registers on the input and output sides respectively.
|
||||||
let mut iregs = self.regs_in.clone();
|
let mut iregs = self.regs_in.clone();
|
||||||
let mut oregs = self.regs_out.clone();
|
let mut oregs = self.regs_out.clone();
|
||||||
|
let mut gregs = global_regs.clone();
|
||||||
|
|
||||||
for v in &mut self.vars {
|
for v in &mut self.vars {
|
||||||
let rc = v.constraint;
|
let rc = v.constraint;
|
||||||
let reg = match v.iter(&iregs, &oregs).next() {
|
let reg = match v.iter(&iregs, &oregs, &gregs).next() {
|
||||||
None => return Err(rc),
|
|
||||||
Some(reg) => reg,
|
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;
|
v.solution = reg;
|
||||||
@@ -832,6 +911,9 @@ impl Solver {
|
|||||||
if v.is_output {
|
if v.is_output {
|
||||||
oregs.take(rc, reg);
|
oregs.take(rc, reg);
|
||||||
}
|
}
|
||||||
|
if v.is_global {
|
||||||
|
gregs.take(rc, reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(oregs)
|
Ok(oregs)
|
||||||
@@ -1083,6 +1165,7 @@ mod tests {
|
|||||||
let r0 = gpr.unit(0);
|
let r0 = gpr.unit(0);
|
||||||
let r1 = gpr.unit(1);
|
let r1 = gpr.unit(1);
|
||||||
let r2 = gpr.unit(2);
|
let r2 = gpr.unit(2);
|
||||||
|
let gregs = AllocatableSet::new();
|
||||||
let mut regs = AllocatableSet::new();
|
let mut regs = AllocatableSet::new();
|
||||||
let mut solver = Solver::new();
|
let mut solver = Solver::new();
|
||||||
let v10 = Value::new(10);
|
let v10 = Value::new(10);
|
||||||
@@ -1093,7 +1176,7 @@ mod tests {
|
|||||||
solver.reset(®s);
|
solver.reset(®s);
|
||||||
solver.reassign_in(v10, gpr, r1, r0);
|
solver.reassign_in(v10, gpr, r1, r0);
|
||||||
solver.inputs_done();
|
solver.inputs_done();
|
||||||
assert!(solver.quick_solve().is_ok());
|
assert!(solver.quick_solve(&gregs).is_ok());
|
||||||
assert_eq!(solver.schedule_moves(®s), 0);
|
assert_eq!(solver.schedule_moves(®s), 0);
|
||||||
assert_eq!(solver.moves(), &[mov(v10, gpr, r1, r0)]);
|
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(v10, gpr, r0, r1);
|
||||||
solver.reassign_in(v11, gpr, r1, r2);
|
solver.reassign_in(v11, gpr, r1, r2);
|
||||||
solver.inputs_done();
|
solver.inputs_done();
|
||||||
assert!(solver.quick_solve().is_ok());
|
assert!(solver.quick_solve(&gregs).is_ok());
|
||||||
assert_eq!(solver.schedule_moves(®s), 0);
|
assert_eq!(solver.schedule_moves(®s), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
solver.moves(),
|
solver.moves(),
|
||||||
@@ -1115,7 +1198,7 @@ mod tests {
|
|||||||
solver.reassign_in(v10, gpr, r0, r1);
|
solver.reassign_in(v10, gpr, r0, r1);
|
||||||
solver.reassign_in(v11, gpr, r1, r0);
|
solver.reassign_in(v11, gpr, r1, r0);
|
||||||
solver.inputs_done();
|
solver.inputs_done();
|
||||||
assert!(solver.quick_solve().is_ok());
|
assert!(solver.quick_solve(&gregs).is_ok());
|
||||||
assert_eq!(solver.schedule_moves(®s), 0);
|
assert_eq!(solver.schedule_moves(®s), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
solver.moves(),
|
solver.moves(),
|
||||||
@@ -1140,6 +1223,7 @@ mod tests {
|
|||||||
let s1 = s.unit(1);
|
let s1 = s.unit(1);
|
||||||
let s2 = s.unit(2);
|
let s2 = s.unit(2);
|
||||||
let s3 = s.unit(3);
|
let s3 = s.unit(3);
|
||||||
|
let gregs = AllocatableSet::new();
|
||||||
let mut regs = AllocatableSet::new();
|
let mut regs = AllocatableSet::new();
|
||||||
let mut solver = Solver::new();
|
let mut solver = Solver::new();
|
||||||
let v10 = Value::new(10);
|
let v10 = Value::new(10);
|
||||||
@@ -1154,7 +1238,7 @@ mod tests {
|
|||||||
solver.reassign_in(v11, s, s2, s0);
|
solver.reassign_in(v11, s, s2, s0);
|
||||||
solver.reassign_in(v12, s, s3, s1);
|
solver.reassign_in(v12, s, s3, s1);
|
||||||
solver.inputs_done();
|
solver.inputs_done();
|
||||||
assert!(solver.quick_solve().is_ok());
|
assert!(solver.quick_solve(&gregs).is_ok());
|
||||||
assert_eq!(solver.schedule_moves(®s), 0);
|
assert_eq!(solver.schedule_moves(®s), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
solver.moves(),
|
solver.moves(),
|
||||||
@@ -1175,7 +1259,7 @@ mod tests {
|
|||||||
solver.reassign_in(v12, s, s1, s3);
|
solver.reassign_in(v12, s, s1, s3);
|
||||||
solver.reassign_in(v10, d, d1, d0);
|
solver.reassign_in(v10, d, d1, d0);
|
||||||
solver.inputs_done();
|
solver.inputs_done();
|
||||||
assert!(solver.quick_solve().is_ok());
|
assert!(solver.quick_solve(&gregs).is_ok());
|
||||||
assert_eq!(solver.schedule_moves(®s), 0);
|
assert_eq!(solver.schedule_moves(®s), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
solver.moves(),
|
solver.moves(),
|
||||||
@@ -1199,6 +1283,7 @@ mod tests {
|
|||||||
let r3 = gpr.unit(3);
|
let r3 = gpr.unit(3);
|
||||||
let r4 = gpr.unit(4);
|
let r4 = gpr.unit(4);
|
||||||
let r5 = gpr.unit(5);
|
let r5 = gpr.unit(5);
|
||||||
|
let gregs = AllocatableSet::new();
|
||||||
let mut regs = AllocatableSet::new();
|
let mut regs = AllocatableSet::new();
|
||||||
let mut solver = Solver::new();
|
let mut solver = Solver::new();
|
||||||
let v10 = Value::new(10);
|
let v10 = Value::new(10);
|
||||||
@@ -1219,7 +1304,7 @@ mod tests {
|
|||||||
solver.reassign_in(v11, gpr, r1, r2);
|
solver.reassign_in(v11, gpr, r1, r2);
|
||||||
solver.reassign_in(v12, gpr, r2, r0);
|
solver.reassign_in(v12, gpr, r2, r0);
|
||||||
solver.inputs_done();
|
solver.inputs_done();
|
||||||
assert!(solver.quick_solve().is_ok());
|
assert!(solver.quick_solve(&gregs).is_ok());
|
||||||
assert_eq!(solver.schedule_moves(®s), 1);
|
assert_eq!(solver.schedule_moves(®s), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
solver.moves(),
|
solver.moves(),
|
||||||
@@ -1243,7 +1328,7 @@ mod tests {
|
|||||||
solver.reassign_in(v15, gpr, r5, r3);
|
solver.reassign_in(v15, gpr, r5, r3);
|
||||||
|
|
||||||
solver.inputs_done();
|
solver.inputs_done();
|
||||||
assert!(solver.quick_solve().is_ok());
|
assert!(solver.quick_solve(&gregs).is_ok());
|
||||||
// We resolve two cycles with one spill.
|
// We resolve two cycles with one spill.
|
||||||
assert_eq!(solver.schedule_moves(®s), 1);
|
assert_eq!(solver.schedule_moves(®s), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
Reference in New Issue
Block a user