diff --git a/cranelift/filetests/regalloc/global-constraints.cton b/cranelift/filetests/regalloc/global-constraints.cton new file mode 100644 index 0000000000..5fc63d0c36 --- /dev/null +++ b/cranelift/filetests/regalloc/global-constraints.cton @@ -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 +} diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index cdea494438..a0375bb6c4 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -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(®s.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(®s.global).unwrap_or_else(|_| { + dbg!("quick_solve failed for {}", self.solver); + 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 // 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); + } + } +} diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 26d47d4b90..3007bcec7b 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -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 diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index 2a2b527561..39f6e3a83c 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -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 { - self.find_solution() + pub fn quick_solve( + &mut self, + global_regs: &AllocatableSet, + ) -> Result { + 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 { - // TODO: Sort variables to assign smallest register classes first. - self.find_solution() + pub fn real_solve( + &mut self, + global_regs: &AllocatableSet, + ) -> Result { + // 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 { + fn find_solution( + &mut self, + global_regs: &AllocatableSet, + ) -> Result { // 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(®s); 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(®s), 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(®s), 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(®s), 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(®s), 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(®s), 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(®s), 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(®s), 1); assert_eq!(