From 893a6716c6b1197a1a52c2d3e121d83e939490ff Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 6 Oct 2017 18:51:36 -0700 Subject: [PATCH] Enforce all instruction constraints in iterate_solution(). During iterate_solution(), live-through values may be converted to solver variables so they can be moved out of the way in order to satisfy all constraints. Make sure that the instruction's operand constraints are also considered for these new variables. Add a program_complete_input_constraints() which turns all the instruction's input operands into variables with the proper constraints. That makes it safe for try_add_var() to re-add these values as variables with looser generic constraints. The solver's add_var() function is split into three functions: add_var for use before inputs_done(), and add_killed_var/add_through_var for use after. --- lib/cretonne/src/regalloc/coloring.rs | 49 +++++++++++-- lib/cretonne/src/regalloc/solver.rs | 99 +++++++++++++++++++-------- 2 files changed, 116 insertions(+), 32 deletions(-) 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, + }); + } } }