diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index 93888b109e..b5062d988c 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -104,7 +104,7 @@ pub struct EncInfo { impl EncInfo { /// Get the value operand constraints for `enc` if it is a legal encoding. - pub fn operand_constraints(&self, enc: Encoding) -> Option<&RecipeConstraints> { + pub fn operand_constraints(&self, enc: Encoding) -> Option<&'static RecipeConstraints> { self.constraints.get(enc.recipe()) } diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index f664c43c48..871d86e757 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -15,7 +15,11 @@ //! values used by the tied operands must be killed by the instruction. This can be achieved by //! inserting a `copy` to a new value immediately before the two-address instruction. //! -//! 3. The register pressure must be lowered sufficiently by inserting spill code. Register +//! 3. If a value is bound to more than one operand on the same instruction, the operand +//! constraints must be compatible. This can also be achieved by inserting copies so the +//! incompatible operands get different values. +//! +//! 4. The register pressure must be lowered sufficiently by inserting spill code. Register //! operands are allowed to read spilled values, but each such instance must be counted as using //! a register. //! @@ -26,30 +30,28 @@ //! a topological order relative to the dominance relation, we can assign colors to the values //! defined by the instruction and only consider the colors of other values that are live at the //! instruction. -//! -//! The topological order of instructions inside an EBB is simply the layout order, starting from -//! the EBB header. A topological order of the EBBs can only visit an EBB once its immediate -//! dominator has been visited. -//! -//! There are many valid topological orders of the EBBs, and the specific order can affect which -//! coloring hints are satisfied and which are broken. -//! use entity_map::EntityMap; use dominator_tree::DominatorTree; use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Signature, ArgumentLoc}; -use isa::{TargetIsa, RegInfo, Encoding, EncInfo, ConstraintKind}; +use isa::{TargetIsa, Encoding, EncInfo, OperandConstraint, ConstraintKind}; +use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; use regalloc::affinity::Affinity; use regalloc::allocatable_set::AllocatableSet; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; +use regalloc::solver::Solver; +use regalloc::RegDiversions; use topo_order::TopoOrder; /// Data structures for the coloring pass. /// /// These are scratch space data structures that can be reused between invocations. -pub struct Coloring {} +pub struct Coloring { + divert: RegDiversions, + solver: Solver, +} /// Bundle of references that the coloring algorithm needs. /// @@ -69,6 +71,13 @@ struct Context<'a> { domtree: &'a DominatorTree, liveness: &'a mut Liveness, + // References to working set data structures. + // If we need to borrow out of a data structure across a method call, it must be passed as a + // function argument instead, see the `LiveValueTracker` arguments. + topo: &'a mut TopoOrder, + divert: &'a mut RegDiversions, + solver: &'a mut Solver, + // Pristine set of registers that the allocator can use. // This set remains immutable, we make clones. usable_regs: AllocatableSet, @@ -77,7 +86,10 @@ struct Context<'a> { impl Coloring { /// Allocate scratch space data structures for the coloring pass. pub fn new() -> Coloring { - Coloring {} + Coloring { + divert: RegDiversions::new(), + solver: Solver::new(), + } } /// Run the coloring algorithm over `func`. @@ -93,26 +105,31 @@ impl Coloring { encinfo: isa.encoding_info(), domtree, liveness, + topo, + divert: &mut self.divert, + solver: &mut self.solver, usable_regs: isa.allocatable_registers(func), }; - ctx.run(func, topo, tracker) + ctx.run(func, tracker) } } impl<'a> Context<'a> { /// Run the coloring algorithm. - fn run(&mut self, func: &mut Function, topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { - // Just visit blocks in layout order, letting `topo` enforce a topological ordering. + fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { + // Just visit blocks in layout order, letting `self.topo` enforce a topological ordering. // TODO: Once we have a loop tree, we could visit hot blocks first. - topo.reset(func.layout.ebbs()); - while let Some(ebb) = topo.next(&func.layout, self.domtree) { + self.topo.reset(func.layout.ebbs()); + while let Some(ebb) = self.topo.next(&func.layout, self.domtree) { self.visit_ebb(ebb, func, tracker); } } /// Visit `ebb`, assuming that the immediate dominator has already been visited. fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + dbg!("Coloring {}:", ebb); let mut regs = self.visit_ebb_header(ebb, func, tracker); + self.divert.clear(); // Now go through the instructions in `ebb` and color the values they define. let mut pos = Cursor::new(&mut func.layout); @@ -168,10 +185,15 @@ impl<'a> Context<'a> { .get(value) .expect("No live range for live-in") .affinity; - if let Affinity::Reg(rc_index) = affinity { - let regclass = self.reginfo.rc(rc_index); - match func.locations[value] { - ValueLoc::Reg(regunit) => regs.take(regclass, regunit), + if let Affinity::Reg(rci) = affinity { + let rc = self.reginfo.rc(rci); + let loc = func.locations[value]; + dbg!("Live-in: {}:{} in {}", + lv.value, + rc, + loc.display(&self.reginfo)); + match loc { + ValueLoc::Reg(reg) => regs.take(rc, reg), ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", value), ValueLoc::Stack(ss) => { panic!("Live-in {} is in {}, should be register", value, ss) @@ -200,11 +222,11 @@ impl<'a> Context<'a> { for (lv, abi) in args.iter().zip(&sig.argument_types) { match lv.affinity { - Affinity::Reg(rc_index) => { - let regclass = self.reginfo.rc(rc_index); - if let ArgumentLoc::Reg(regunit) = abi.location { - regs.take(regclass, regunit); - *locations.ensure(lv.value) = ValueLoc::Reg(regunit); + Affinity::Reg(rci) => { + let rc = self.reginfo.rc(rci); + if let ArgumentLoc::Reg(reg) = abi.location { + regs.take(rc, reg); + *locations.ensure(lv.value) = ValueLoc::Reg(reg); } else { // This should have been fixed by the reload pass. panic!("Entry arg {} has {} affinity, but ABI {}", @@ -247,14 +269,14 @@ impl<'a> Context<'a> { -> AllocatableSet { for lv in args { // Only look at the register arguments. - if let Affinity::Reg(rc_index) = lv.affinity { - let regclass = self.reginfo.rc(rc_index); + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); // TODO: Fall back to a top-level super-class. Sub-classes are only hints. - let regunit = regs.iter(regclass) + let reg = regs.iter(rc) .next() .expect("Out of registers for arguments"); - regs.take(regclass, regunit); - *locations.ensure(lv.value) = ValueLoc::Reg(regunit); + regs.take(rc, reg); + *locations.ensure(lv.value) = ValueLoc::Reg(reg); } } @@ -266,7 +288,7 @@ impl<'a> Context<'a> { /// /// Update `regs` to reflect the allocated registers after `inst`, including removing any dead /// or killed values from the set. - fn visit_inst(&self, + fn visit_inst(&mut self, inst: Inst, encoding: Encoding, _pos: &mut Cursor, @@ -274,95 +296,194 @@ impl<'a> Context<'a> { tracker: &mut LiveValueTracker, regs: &mut AllocatableSet, locations: &mut EntityMap) { - // First update the live value tracker with this instruction. - // Get lists of values that are killed and defined by `inst`. - let (_throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + dbg!("Coloring [{}] {}", + self.encinfo.display(encoding), + dfg.display_inst(inst)); // Get the operand constraints for `inst` that we are trying to satisfy. let constraints = self.encinfo .operand_constraints(encoding) - .expect("Missing instruction encoding") - .clone(); + .expect("Missing instruction encoding"); + + // Program the solver with register constraints for the input side. + self.solver.reset(regs); + self.program_input_constraints(inst, constraints.ins, dfg, locations); + if self.solver.has_fixed_input_conflicts() { + self.divert_fixed_input_conflicts(tracker.live(), locations); + } + self.solver.inputs_done(); + + // Update the live value tracker with this instruction. + let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); // Get rid of the killed values. for lv in kills { - if let Affinity::Reg(rc_index) = lv.affinity { - let regclass = self.reginfo.rc(rc_index); - if let ValueLoc::Reg(regunit) = locations[lv.value] { - regs.free(regclass, regunit); - } + if let Affinity::Reg(rci) = lv.affinity { + self.solver + .add_kill(lv.value, + self.reginfo.rc(rci), + self.divert.reg(lv.value, locations)); } } - // Process the defined values with fixed constraints. - // TODO: Handle constraints on call return values. - assert_eq!(defs.len(), - constraints.outs.len(), - "Can't handle variable results"); - for (lv, opcst) in defs.iter().zip(constraints.outs) { - match lv.affinity { - // This value should go in a register. - Affinity::Reg(rc_index) => { - // The preferred register class is not a requirement. - let pref_rc = self.reginfo.rc(rc_index); - match opcst.kind { - ConstraintKind::Reg => { - // This is a standard register constraint. The preferred register class - // should have been computed as a subclass of the hard constraint of - // the def. - assert!(opcst.regclass.has_subclass(rc_index), - "{} preference {} is not compatible with the definition \ - constraint {}", - lv.value, - pref_rc.name, - opcst.regclass.name); - // Try to grab a register from the preferred class, but fall back to - // the actual constraint if we have to. - let regunit = regs.iter(pref_rc) - .next() - .or_else(|| regs.iter(opcst.regclass).next()) - .expect("Ran out of registers"); - regs.take(opcst.regclass, regunit); - *locations.ensure(lv.value) = ValueLoc::Reg(regunit); - } - ConstraintKind::Tied(arg_index) => { - // This def must use the same register as a fixed instruction argument. - let arg = dfg.inst_args(inst)[arg_index as usize]; - let loc = locations[arg]; - *locations.ensure(lv.value) = loc; - // Mark the reused register. It's not really clear if we support tied - // stack operands. We could do that for some Intel read-modify-write - // encodings. - if let ValueLoc::Reg(regunit) = loc { - // This is going to assert out unless the incoming value at - // `arg_index` was killed. Tied operands must be fixed to - // ensure that before running the coloring pass. - regs.take(opcst.regclass, regunit); - } - } - ConstraintKind::FixedReg(_regunit) => unimplemented!(), - ConstraintKind::Stack => { - panic!("{}:{} should be a stack value", lv.value, pref_rc.name) - } - } - } - Affinity::Stack => unimplemented!(), - Affinity::None => { - panic!("Encoded instruction defines {} with no affinity", lv.value) - } - } + // 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. + if constraints.fixed_outs { + self.program_fixed_output_constraints(inst, + constraints.outs, + defs, + throughs, + dfg, + locations); + } + self.program_output_constraints(inst, constraints.outs, defs, dfg, locations); + + // 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(|_| self.iterate_solution()); + + // Apply the solution to the defs. + for v in self.solver.vars().iter().filter(|&v| v.is_define) { + *locations.ensure(v.value) = ValueLoc::Reg(v.solution); } - // Get rid of the dead defs. + // Update `regs` for the next instruction, remove the dead defs. for lv in defs { if lv.endpoint == inst { - if let Affinity::Reg(rc_index) = lv.affinity { - let regclass = self.reginfo.rc(rc_index); - if let ValueLoc::Reg(regunit) = locations[lv.value] { - regs.free(regclass, regunit); + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + let reg = self.divert.reg(lv.value, locations); + output_regs.free(rc, reg); + } + } + } + *regs = output_regs; + } + + /// Program the input-side constraints for `inst` into the constraint solver. + fn program_input_constraints(&mut self, + inst: Inst, + constraints: &[OperandConstraint], + dfg: &mut DataFlowGraph, + locations: &mut EntityMap) { + for (op, &value) in constraints + .iter() + .zip(dfg.inst_args(inst)) + .filter(|&(op, _)| op.kind != ConstraintKind::Stack) { + // Reload pass is supposed to ensure that all arguments to register operands are + // already in a register. + let cur_reg = self.divert.reg(value, locations); + match op.kind { + ConstraintKind::FixedReg(regunit) => { + if regunit != cur_reg { + self.solver + .reassign_in(value, op.regclass, cur_reg, regunit); } } + ConstraintKind::Reg | + ConstraintKind::Tied(_) => { + if !op.regclass.contains(cur_reg) { + self.solver + .add_var(value, op.regclass, cur_reg, &self.reginfo); + } + } + ConstraintKind::Stack => unreachable!(), + } + } + } + + // Find existing live values that conflict with the fixed input register constraints programmed + // into the constraint solver. Convert them to solver variables so they can be diverted. + fn divert_fixed_input_conflicts(&mut self, + live: &[LiveValue], + locations: &mut EntityMap) { + for lv in live { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + let reg = self.divert.reg(lv.value, locations); + if self.solver.is_fixed_input_conflict(rc, reg) { + self.solver.add_var(lv.value, rc, reg, &self.reginfo); + } } } } + + /// Program any fixed-register output constraints into the solver. This may also detect + /// conflicts between live-through registers and fixed output registers. These live-through + /// values need to be turned into solver variables so they can be reassigned. + fn program_fixed_output_constraints(&mut self, + _inst: Inst, + constraints: &[OperandConstraint], + defs: &[LiveValue], + throughs: &[LiveValue], + _dfg: &mut DataFlowGraph, + locations: &mut EntityMap) { + for (op, lv) in constraints.iter().zip(defs) { + if let ConstraintKind::FixedReg(reg) = op.kind { + self.add_fixed_output(lv.value, op.regclass, reg, throughs, locations); + } + } + } + + /// Add a single fixed output value to the solver. + fn add_fixed_output(&mut self, + value: Value, + rc: RegClass, + reg: RegUnit, + throughs: &[LiveValue], + locations: &mut EntityMap) { + if !self.solver.add_fixed_output(rc, reg) { + // The fixed output conflicts with some of the live-through registers. + for lv in throughs { + if let Affinity::Reg(rci) = lv.affinity { + let rc2 = self.reginfo.rc(rci); + let reg2 = self.divert.reg(lv.value, locations); + if regs_overlap(rc, reg, rc2, reg2) { + // This live-through value is interfering with the fixed output assignment. + // Convert it to a solver variable. + // TODO: Use a looser constraint than the affinity hint. Any allocatable + // register in the top-level register class would be OK. Maybe `add_var` + // should take both a preferred class and a required constraint class. + self.solver.add_var(lv.value, rc2, reg2, &self.reginfo); + } + } + } + + let ok = self.solver.add_fixed_output(rc, reg); + assert!(ok, "Couldn't clear fixed output interference for {}", value); + } + *locations.ensure(value) = ValueLoc::Reg(reg); + } + + /// Program the output-side constraints for `inst` into the constraint solver. + /// + /// It is assumed that all fixed outputs have already been handled. + fn program_output_constraints(&mut self, + _inst: Inst, + constraints: &[OperandConstraint], + defs: &[LiveValue], + _dfg: &mut DataFlowGraph, + _locations: &mut EntityMap) { + for (op, lv) in constraints.iter().zip(defs) { + match op.kind { + ConstraintKind::FixedReg(_) | + ConstraintKind::Stack => continue, + ConstraintKind::Reg => { + self.solver.add_def(lv.value, op.regclass); + } + ConstraintKind::Tied(_) => unimplemented!(), + } + } + } + + /// Try harder to find a solution to the constraint problem since `quick_solve()` failed. + /// + /// 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(&self) -> AllocatableSet { + unimplemented!(); + } } diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 9803c3af3f..fd2e78e9c3 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -11,6 +11,7 @@ pub mod coloring; mod affinity; mod context; mod diversion; +mod solver; pub use self::allocatable_set::AllocatableSet; pub use self::context::Context; diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs new file mode 100644 index 0000000000..05751c5155 --- /dev/null +++ b/lib/cretonne/src/regalloc/solver.rs @@ -0,0 +1,536 @@ +//! Constraint solver for register coloring. +//! +//! The coloring phase of SSA-based register allocation is very simple in theory, but in practice +//! it is complicated by the various constraints imposed by individual instructions: +//! +//! - Call and return instructions have to satisfy ABI requirements for arguments and return +//! values. +//! - Values live across a call must be in a callee-saved register. +//! - Some instructions have operand constraints such as register sub-classes, fixed registers, or +//! tied operands. +//! +//! # The instruction register coloring problem +//! +//! The constraint solver addresses the problem of satisfying the constraints of a single +//! instruction. We have: +//! +//! - A set of values that are live in registers before the instruction, with current register +//! assignments. Some are used by the instruction, some are not. +//! - A subset of the live register values that are killed by the instruction. +//! - A set of new register values that are defined by the instruction. +//! The constraint solver addresses the problem of satisfying the constraints of a single +//! instruction. We have: +//! +//! - A set of values that are live in registers before the instruction, with current register +//! assignments. Some are used by the instruction, some are not. +//! - A subset of the live register values that are killed by the instruction. +//! - A set of new register values that are defined by the instruction. +//! +//! We are not concerned with stack values at all. The reload pass ensures that all values required +//! to be in a register by the instruction are already in a register. +//! +//! A solution to the register coloring problem consists of: +//! +//! - Register reassignment prescriptions for a subset of the live register values. +//! - Register assignments for the defined values. +//! +//! The solution ensures that when live registers are reassigned as prescribed before the +//! instruction, all its operand constraints are satisfied, and the definition assignments won't +//! conflict. +//! +//! # Register diversions and global interference +//! +//! We can divert register values temporarily to satisfy constraints, but we need to put the +//! values back into their originally assigned register locations before leaving the EBB. +//! Otherwise, values won't be in the right register at the entry point of other EBBs. +//! +//! Some values are *local*, and we don't need to worry about putting those values back since they +//! are not used in any other EBBs. +//! +//! When we assign register locations to defines, we are assigning both the register used locally +//! immediately after the instruction and the register used globally when the defined value is used +//! in a different EBB. We need to avoid interference both locally at the instruction and globally. +//! +//! We have multiple mappings of values to registers: +//! +//! 1. The initial local mapping before the instruction. This includes any diversions from previous +//! instructions in the EBB, but not diversions for the current instruction. +//! 2. The local mapping after applying the additional reassignments required to satisfy the +//! constraints of the current instruction. +//! 3. The local mapping after the instruction. This excludes values killed by the instruction and +//! includes values defined by the instruction. +//! 4. The global mapping after the instruction. This mapping only contains values with global live +//! ranges, and it does not include any diversions. +//! +//! All four mappings must be kept free of interference. +//! +//! # Problems handled by previous passes. +//! +//! The constraint solver can only reassign registers, it can't create spill code, so some +//! constraints are handled by earlier passes: +//! +//! - There will be enough free registers available for the defines. Ensuring this is the primary +//! purpose of the spilling phase. +//! - When the same value is used for multiple operands, the intersection of operand constraints is +//! non-empty. The spilling phase will insert copies to handle mutually incompatible constraints, +//! such as when the same value is bound to two different function arguments. +//! - Values bound to tied operands must be killed by the instruction. Also enforced by the +//! spiller. +//! - Values used by register operands are in registers, and values used by stack operands are in +//! stack slots. This is enforced by the reload pass. +//! +//! # Solver algorithm +//! +//! The goal of the solver is to satisfy the instruction constraints with a minimal number of +//! register assignments before the instruction. +//! +//! 1. Compute the set of values used by operands with a fixed register constraint that isn't +//! already satisfied. These are mandatory predetermined reassignments. +//! 2. Compute the set of values that don't satisfy their register class constraint. These are +//! mandatory reassignments that we need to solve. +//! 3. Add the set of defines to the set of variables computed in 2. Exclude defines tied to an +//! input operand since their value is pre-determined. +//! +//! The set of values computed in 2. and 3. are the *variables* for the solver. Given a set of +//! variables, we can also compute a set of allocatable registers by removing the variables from +//! the set of assigned registers before the instruction. +//! +//! 1. For each variable, compute its domain as the intersection of the allocatable registers and +//! its register class constraint. +//! 2. Sort the variables in order of increasing domain size. +//! 3. Search for a solution that assigns each variable a register from its domain without +//! interference between variables. +//! +//! If the search fails to find a solution, we may need to reassign more registers. Find an +//! appropriate candidate among the set of live register values, add it as a variable and start +//! over. + +use ir::Value; +use isa::{RegInfo, RegClass, RegUnit}; +use regalloc::allocatable_set::RegSetIter; +use sparse_map::{SparseMap, SparseMapValue}; +use std::fmt; +use super::AllocatableSet; + +/// A variable in the constraint problem. +/// +/// Variables represent register values that can be assigned to any register unit within the +/// constraint register class. This includes live register values that can be reassigned to a new +/// register and values defined by the instruction which must be assigned to a register. +/// +/// Besides satisfying the register class constraint, variables must also be mutually +/// non-interfering in up to three contexts: +/// +/// 1. Input side live registers, after applying all the reassignments. +/// 2. Output side live registers, considering all the local register diversions. +/// 3. Global live register, not considering any local diversions. +/// +pub struct Variable { + /// The value whose register assignment we're looking for. + pub value: Value, + + /// Avoid interference on the input side. + is_input: bool, + + /// Avoid interference on the output side. + is_output: bool, + + /// Avoid interference with the global registers. + is_global: bool, + + /// The value is defined by the current instruction. + pub is_define: bool, + + /// Number of registers available in the domain of this variable. + domain: u16, + + /// The assigned register unit after a full solution was found. + pub solution: RegUnit, + + /// Any solution must belong to the constraint register class. + constraint: RegClass, +} + +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) + } + } +} + +impl fmt::Display for Variable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}({}", self.value, self.constraint)?; + if self.is_input { + write!(f, ", in")?; + } + if self.is_output { + write!(f, ", out")?; + } + if self.is_global { + write!(f, ", global")?; + } + if self.is_define { + write!(f, ", def")?; + } + if self.domain > 0 { + write!(f, ", {}", self.domain)?; + } + write!(f, ")") + } +} + + +struct Assignment { + value: Value, + from: RegUnit, + to: RegUnit, + rc: RegClass, +} + +impl SparseMapValue for Assignment { + fn key(&self) -> Value { + self.value + } +} + +/// Constraint solver for register allocation around a single instruction. +/// +/// Start by programming in the instruction constraints. +/// +/// 1. Initialize the solver by calling `reset()` with the set of allocatable registers before the +/// instruction. +/// 2. Program the input side constraints: Call `reassign_in()` for all fixed register constraints, +/// and `add_var()` for any input operands whose constraints are not already satisfied. +/// 3. Check for conflicts between fixed input assignments and existing live values by calling +/// `has_fixed_input_conflicts()`. Resolve any conflicts by calling `add_var()` with the +/// conflicting values. +/// 4. Prepare for adding output side constraints by calling `inputs_done()`. +/// 5. Add any killed register values that no longer cause interference on the output side by +/// 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()`. +/// +pub struct Solver { + /// Register reassignments that are required or decided as part of a full solution. + assignments: SparseMap, + + /// Variables are the values that should be reassigned as part of a solution. + /// Values with a fixed register constraints are not considered variables. They are represented + /// in the `assignments` vector if necessary. + vars: Vec, + + /// Are we finished adding input-side constraints? This changes the meaning of the `regs_in` + /// and `regs_out` register sets. + inputs_done: bool, + + /// Available registers on the input side of the instruction. + /// + /// While we're adding input constraints (`!inputs_done`): + /// + /// - Live values on the input side are marked as unavailable. + /// - The 'from' registers of fixed input reassignments are marked as available as they are + /// added. + /// - Input-side variables are marked as available. + /// + /// After finishing input constraints (`inputs_done`): + /// + /// - Live values on the input side are marked as unavailable. + /// - The 'to' registers of fixed input reassignments are marked as unavailable. + /// - Input-side variables are marked as available. + /// + regs_in: AllocatableSet, + + /// Available registers on the output side of the instruction / fixed input scratch space. + /// + /// While we're adding input constraints (`!inputs_done`): + /// + /// - The 'to' registers of fixed input reassignments are marked as unavailable. + /// + /// After finishing input constraints (`inputs_done`): + /// + /// - Live-through values are marked as unavailable. + /// - Fixed output assignments are marked as unavailable. + /// - Live-through variables are marked as available. + /// + regs_out: AllocatableSet, +} + +/// Interface for programming the constraints into the solver. +impl Solver { + /// Create a new empty solver. + pub fn new() -> Solver { + Solver { + assignments: SparseMap::new(), + vars: Vec::new(), + inputs_done: false, + regs_in: AllocatableSet::new(), + regs_out: AllocatableSet::new(), + } + } + + /// Reset the solver state and prepare solving for a new instruction with an initial set of + /// allocatable registers. + /// + /// The `regs` set is the allocatable registers before any reassignments are applied. + pub fn reset(&mut self, regs: &AllocatableSet) { + self.assignments.clear(); + self.vars.clear(); + self.inputs_done = false; + self.regs_in = regs.clone(); + // Used for tracking fixed input assignments while `!inputs_done`: + self.regs_out = AllocatableSet::new(); + } + + /// Add a fixed input reassignment of `value`. + /// + /// This means that `value` must be assigned to `to` and can't become a variable. Call with + /// `from == to` to ensure that `value` is not reassigned from its existing register location. + /// + /// In either case, `to` will not be available for variables on the input side of the + /// instruction. + pub fn reassign_in(&mut self, value: Value, rc: RegClass, from: RegUnit, to: RegUnit) { + debug_assert!(!self.inputs_done); + if self.regs_in.is_avail(rc, from) { + // It looks like `value` was already removed from the register set. It must have been + // added as a variable previously. A fixed constraint beats a variable, so convert it. + if let Some(idx) = self.vars.iter().position(|v| v.value == value) { + let v = self.vars.remove(idx); + dbg!("Converting variable {} to a fixed constraint", v); + // The spiller is responsible for ensuring that all constraints on the uses of a + // value are compatible. + assert!(v.constraint.contains(to), + "Incompatible constraints for {}", + value); + } else { + panic!("Invalid from register for fixed {} constraint", value); + } + } + self.regs_in.free(rc, from); + self.regs_out.take(rc, to); + self.assignments + .insert(Assignment { + value, + rc, + from, + to, + }); + } + + /// Add a variable representing an input side value with an existing register assignment. + /// + /// A variable is a value that should be reassigned to something in the `constraint` register + /// class. + /// + /// 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()`. + pub fn add_var(&mut self, + value: Value, + constraint: RegClass, + from: RegUnit, + reginfo: &RegInfo) { + // Check for existing entries for this value. + if self.regs_in.is_avail(constraint, from) { + // There cold be an existing variable entry. + if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) { + // We have an existing variable entry for `value`. Combine the constraints. + if let Some(rci) = v.constraint.intersect(constraint) { + v.constraint = reginfo.rc(rci); + return; + } else { + // The spiller should have made sure the same value is not used with disjoint + // constraints. + panic!("Incompatible constraints: {} + {}", constraint, *v) + } + } + + // No variable, then it must be a fixed reassignment. + if let Some(a) = self.assignments.get(value) { + assert!(constraint.contains(a.to), + "Incompatible constraints for {}", + value); + return; + } + + panic!("Wrong from register for {}", value); + } + self.regs_in.free(constraint, from); + if self.inputs_done { + self.regs_out.free(constraint, from); + } + self.vars + .push(Variable { + value, + constraint, + is_input: true, + is_output: true, + is_global: false, + is_define: false, + domain: 0, + solution: !0, + }); + } + + /// Check for conflicts between fixed input assignments and existing live values. + /// + /// Returns true if one of the live values conflicts with a fixed input assignment. Such a + /// conflicting value must be turned into a variable. + pub fn has_fixed_input_conflicts(&self) -> bool { + debug_assert!(!self.inputs_done); + // The `from` side of the fixed input diversions are taken from `regs_out`. + self.regs_out.interferes_with(&self.regs_in) + } + + /// Check if `rc, reg` specifically conflicts with the fixed input assignments. + pub fn is_fixed_input_conflict(&self, rc: RegClass, reg: RegUnit) -> bool { + debug_assert!(!self.inputs_done); + !self.regs_out.is_avail(rc, reg) + } + + /// Finish adding input side constraints. + /// + /// Call this method to indicate that there will be no more fixed input reassignments added + /// and prepare for the output side constraints. + pub fn inputs_done(&mut self) { + assert!(!self.has_fixed_input_conflicts()); + + // At this point, `regs_out` contains the `to` side of the input reassignments, and the + // `from` side has already been marked as available in `regs_in`. + // + // Remove the `to` assignments from `regs_in` so it now indicates the registers available + // to variables at the input side. + self.regs_in.intersect(&self.regs_out); + + // The meaning of `regs_out` now changes completely to indicate the registers available to + // variables on the output side. + // The initial mask will be modified by `add_kill()` and `add_fixed_output()`. + self.regs_out = self.regs_in.clone(); + + // Now we can't add more fixed input assignments, but `add_var()` is still allowed. + self.inputs_done = true; + } + + /// Record that an input register value is killed by the instruction. + /// + /// Even if a fixed reassignment has been added for the value, the `reg` argument should be the + /// original location before the reassignments. + /// + /// This means that the register is available on the output side. + pub fn add_kill(&mut self, value: Value, rc: RegClass, reg: RegUnit) { + debug_assert!(self.inputs_done); + + // If a fixed assignment is killed, the `to` register becomes available on the output side. + if let Some(a) = self.assignments.get(value) { + debug_assert_eq!(a.from, reg); + self.regs_out.free(a.rc, a.to); + return; + } + + // It's also possible that a variable is killed. That means it doesn't need to satisfy + // interference constraints on the output side. + // Variables representing tied operands will get their `is_output` flag set again later. + if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) { + assert!(v.is_input); + v.is_output = false; + return; + } + + // Alright, this is just a boring value being killed by the instruction. Just reclaim + // the assigned register. + self.regs_out.free(rc, reg); + } + + /// Add a fixed output assignment. + /// + /// This means that `to` will not be available for variables on the output side of the + /// instruction. + /// + /// Returns `false` if a live value conflicts with `to`, so it couldn't be added. Find the + /// conflicting live-through value and turn it into a variable before calling this method + /// again. + #[allow(dead_code)] + pub fn add_fixed_output(&mut self, rc: RegClass, reg: RegUnit) -> bool { + debug_assert!(self.inputs_done); + if self.regs_out.is_avail(rc, reg) { + self.regs_out.take(rc, reg); + true + } else { + false + } + } + + /// 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) { + debug_assert!(self.inputs_done); + self.vars + .push(Variable { + value, + constraint, + is_input: false, + is_output: true, + is_global: false, + is_define: true, + domain: 0, + solution: !0, + }); + } +} + +/// Interface for searching for a solution. +impl Solver { + /// Try a quick-and-dirty solution. + /// + /// This is expected to succeed for most instructions since the constraint problem is almost + /// always trivial. + /// + /// Returns `true` is a solution was found. + pub fn quick_solve(&mut self) -> Result { + self.find_solution() + } + + /// Search for a solution with the current list of variables. + /// + /// 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 { + // Available registers on the input and output sides respectively. + let mut iregs = self.regs_in.clone(); + let mut oregs = self.regs_out.clone(); + + for v in &mut self.vars { + let rc = v.constraint; + let reg = match v.iter(&iregs, &oregs).next() { + None => return Err(rc), + Some(reg) => reg, + }; + + v.solution = reg; + if v.is_input { + iregs.take(rc, reg); + } + if v.is_output { + oregs.take(rc, reg); + } + } + + Ok(oregs) + } + + /// Get all the variables. + pub fn vars(&self) -> &[Variable] { + &self.vars + } +}