diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 8c4de9163f..cdea494438 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -374,7 +374,7 @@ impl<'a> Context<'a> { // 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 registers in {}", rc); + dbg!("quick_solve needs more {} regs for {}", rc, self.solver); self.iterate_solution(throughs) }); @@ -454,6 +454,44 @@ impl<'a> Context<'a> { } } + /// Program the complete set of input constraints into the solver. + /// + /// The `program_input_constraints()` function above will not tell the solver about any values + /// that are already assigned to appropriate registers. This is normally fine, but if we want + /// to add additional variables to help the solver, we need to make sure that they are + /// constrained properly. + /// + /// This function completes the work of `program_input_constraints()` by calling `add_var` for + /// all values used by the instruction. + fn program_complete_input_constraints(&mut self) { + let inst = self.cur.current_inst().expect("Not on an instruction"); + let constraints = self.encinfo + .operand_constraints(self.cur.func.encodings[inst]) + .expect("Current instruction not encoded") + .ins; + + for (op, &value) in constraints.iter().zip(self.cur.func.dfg.inst_args(inst)) { + match op.kind { + ConstraintKind::Reg | + ConstraintKind::Tied(_) => { + let cur_reg = self.divert.reg(value, &self.cur.func.locations); + // This is the opposite condition of `program_input_constraints()`. + if op.regclass.contains(cur_reg) { + // This code runs after calling `solver.inputs_done()` so we must identify + // the new variable as killed or live-through. + let layout = &self.cur.func.layout; + if self.liveness[value].killed_at(inst, layout.pp_ebb(inst), layout) { + self.solver.add_killed_var(value, op.regclass, cur_reg); + } else { + self.solver.add_through_var(value, op.regclass, cur_reg); + } + } + } + _ => {} + } + } + } + /// Prepare for a branch to `dest`. /// /// 1. Any values that are live-in to `dest` must be un-diverted so they live in their globally @@ -638,7 +676,7 @@ impl<'a> Context<'a> { if regs_overlap(rc, reg, toprc2, reg2) { // This live-through value is interfering with the fixed output assignment. // Convert it to a solver variable. - self.solver.add_var(lv.value, toprc2, reg2); + self.solver.add_through_var(lv.value, toprc2, reg2); } } } @@ -684,8 +722,11 @@ 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 { + // 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 {} variables", self.solver.vars().len()); + dbg!("real_solve for {}", self.solver); let rc = match self.solver.real_solve() { Ok(regs) => return regs, Err(rc) => rc, @@ -713,7 +754,7 @@ impl<'a> Context<'a> { if rc.contains(reg2) && self.solver.can_add_var(lv.value, toprc2, reg2) && !self.is_live_on_outgoing_edge(lv.value) { - self.solver.add_var(lv.value, toprc2, reg2); + self.solver.add_through_var(lv.value, toprc2, reg2); return true; } } diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index f21993a4c7..2a2b527561 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -148,13 +148,13 @@ pub struct Variable { } impl Variable { - fn new_live(value: Value, constraint: RegClass, from: RegUnit) -> Variable { + fn new_live(value: Value, constraint: RegClass, from: RegUnit, is_output: bool) -> Variable { Variable { value, constraint, from: Some(from), is_input: true, - is_output: true, + is_output, is_global: false, domain: 0, solution: !0, @@ -430,7 +430,7 @@ impl fmt::Debug for Move { /// calling `add_kill()`. /// 6. Program the output side constraints: Call `add_fixed_output()` for all fixed register /// constraints and `add_def()` for free defines. Resolve fixed output conflicts by calling -/// `add_var()`. +/// `add_through_var()`. /// pub struct Solver { /// Register reassignments that are required or decided as part of a full solution. @@ -565,28 +565,74 @@ impl Solver { /// /// It is assumed initially that the value is also live on the output side of the instruction. /// This can be changed by calling to `add_kill()`. + /// + /// This function can only be used before calling `inputs_done()`. Afterwards, more input-side + /// variables can be added by calling `add_killed_var()` and `add_through_var()` pub fn add_var(&mut self, value: Value, constraint: RegClass, from: RegUnit) { + dbg!( + "add_var({}:{}, from={})", + value, + constraint, + constraint.info.display_regunit(from) + ); + debug_assert!(!self.inputs_done); + self.add_live_var(value, constraint, from, true); + } + + /// Add an extra input-side variable representing a value that is killed by the current + /// instruction. + /// + /// This function should be called after `inputs_done()` only. Use `add_var()` before. + pub fn add_killed_var(&mut self, value: Value, constraint: RegClass, from: RegUnit) { + dbg!( + "add_killed_var({}:{}, from={})", + value, + constraint, + constraint.info.display_regunit(from) + ); + debug_assert!(self.inputs_done); + self.add_live_var(value, constraint, from, false); + } + + /// Add an extra input-side variable representing a value that is live through the current + /// instruction. + /// + /// This function should be called after `inputs_done()` only. Use `add_var()` before. + pub fn add_through_var(&mut self, value: Value, constraint: RegClass, from: RegUnit) { + dbg!( + "add_through_var({}:{}, from={})", + value, + constraint, + constraint.info.display_regunit(from) + ); + debug_assert!(self.inputs_done); + self.add_live_var(value, constraint, from, true); + } + + /// Shared code for `add_var`, `add_killed_var`, and `add_through_var`. + /// + /// Add a variable that is live before the instruction, and possibly live through. Merge + /// constraints if the value has already been added as a variable or fixed assignment. + fn add_live_var( + &mut self, + value: Value, + constraint: RegClass, + from: RegUnit, + live_through: bool, + ) { // Check for existing entries for this value. if self.regs_in.is_avail(constraint, from) { - dbg!( - "add_var({}:{}, from={}) for existing entry", - value, - constraint, - constraint.info.display_regunit(from), - ); - // There could be an existing variable entry. if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) { - dbg!("-> combining constraint with {}", v); - // We have an existing variable entry for `value`. Combine the constraints. if let Some(rc) = v.constraint.intersect(constraint) { + dbg!("-> combining constraint with {} yields {}", v, rc); v.constraint = rc; return; } else { // The spiller should have made sure the same value is not used with disjoint // constraints. - panic!("Incompatible constraints: {} + {}", constraint, *v) + panic!("Incompatible constraints: {} + {}", constraint, v) } } @@ -605,17 +651,11 @@ impl Solver { panic!("Wrong from register for {}", value); } - let new_var = Variable::new_live(value, constraint, from); - dbg!( - "add_var({}:{}, from={}) new entry: {}", - value, - constraint, - constraint.info.display_regunit(from), - new_var - ); + let new_var = Variable::new_live(value, constraint, from, live_through); + dbg!("-> new var: {}", new_var); self.regs_in.free(constraint, from); - if self.inputs_done { + if self.inputs_done && live_through { self.regs_out.free(constraint, from); } self.vars.push(new_var); @@ -817,12 +857,15 @@ impl Solver { // Collect moves from the chosen solution for all non-define variables. for v in &self.vars { if let Some(from) = v.from { - self.moves.push(Move::Reg { - value: v.value, - from, - to: v.solution, - rc: v.constraint, - }); + // Omit variable solutions that don't require the value to be moved. + if from != v.solution { + self.moves.push(Move::Reg { + value: v.value, + from, + to: v.solution, + rc: v.constraint, + }); + } } }