//! An optimizer for a set of peephole optimizations. use crate::instruction_set::InstructionSet; use crate::linear::{bool_to_match_result, Action, Else, MatchOp, MatchResult}; use crate::operator::UnquoteOperator; use crate::optimizations::PeepholeOptimizations; use crate::part::{Constant, Part}; use crate::r#type::{BitWidth, Type}; use peepmatic_automata::State; use std::convert::TryFrom; use std::fmt::{self, Debug}; use std::mem; use std::num::NonZeroU32; /// A peephole optimizer instance that can apply a set of peephole /// optimizations to instructions. /// /// These are created from a set of peephole optimizations with the /// [`PeepholeOptimizer::instance`][crate::PeepholeOptimizer::instance] method. /// /// Reusing an instance when applying peephole optimizations to different /// instruction sequences means that you reuse internal allocations that are /// used to match left-hand sides and build up right-hand sides. pub struct PeepholeOptimizer<'peep, 'ctx, I> where I: InstructionSet<'ctx>, { pub(crate) peep_opt: &'peep PeepholeOptimizations, pub(crate) instr_set: I, pub(crate) right_hand_sides: Vec>, pub(crate) actions: Vec, pub(crate) backtracking_states: Vec<(State, usize)>, } impl<'peep, 'ctx, I> Debug for PeepholeOptimizer<'peep, 'ctx, I> where I: InstructionSet<'ctx>, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let PeepholeOptimizer { peep_opt, instr_set: _, right_hand_sides, actions, backtracking_states, } = self; f.debug_struct("PeepholeOptimizer") .field("peep_opt", peep_opt) .field("instr_set", &"_") .field("right_hand_sides", right_hand_sides) .field("actions", actions) .field("backtracking_states", backtracking_states) .finish() } } impl<'peep, 'ctx, I> PeepholeOptimizer<'peep, 'ctx, I> where I: InstructionSet<'ctx>, { fn eval_unquote_1(&self, operator: UnquoteOperator, a: Constant) -> Constant { use Constant::*; macro_rules! map_int { ( $c:expr , | $x:ident | $e:expr ) => { match $c { Int($x, w) => Int($e, w), Bool(..) => panic!("not an integer"), } }; } match operator { UnquoteOperator::Log2 => map_int!(a, |x| x.trailing_zeros() as _), UnquoteOperator::Neg => map_int!(a, |x| x.wrapping_neg()), UnquoteOperator::Band | UnquoteOperator::Bor | UnquoteOperator::Bxor | UnquoteOperator::Iadd | UnquoteOperator::Imul => unreachable!("not a unary unquote operator: {:?}", operator), } } fn eval_unquote_2(&self, operator: UnquoteOperator, a: Constant, b: Constant) -> Constant { use Constant::*; macro_rules! fold_ints { ( $c1:expr , $c2:expr , | $x:ident , $y:ident | $e:expr ) => { match ($c1, $c2) { (Int($x, w1), Int($y, w2)) if w1 == w2 => Int($e, w1), _ => panic!("not two integers of the same width"), } }; } match operator { UnquoteOperator::Band => fold_ints!(a, b, |x, y| x & y), UnquoteOperator::Bor => fold_ints!(a, b, |x, y| x | y), UnquoteOperator::Bxor => fold_ints!(a, b, |x, y| x ^ y), UnquoteOperator::Iadd => fold_ints!(a, b, |x, y| x.wrapping_add(y)), UnquoteOperator::Imul => fold_ints!(a, b, |x, y| x.wrapping_mul(y)), UnquoteOperator::Log2 | UnquoteOperator::Neg => { unreachable!("not a binary unquote operator: {:?}", operator) } } } fn eval_actions(&mut self, context: &mut I::Context, root: I::Instruction) { let mut actions = mem::replace(&mut self.actions, vec![]); for action in actions.drain(..) { log::trace!("Evaluating action: {:?}", action); match action { Action::GetLhs { path } => { let path = self.peep_opt.paths.lookup(path); let lhs = self .instr_set .get_part_at_path(context, root, path) .expect("should always get part at path OK by the time it is bound"); self.right_hand_sides.push(lhs); } Action::UnaryUnquote { operator, operand } => { let operand = self.right_hand_sides[operand.0 as usize]; let operand = match operand { Part::Instruction(i) => self .instr_set .instruction_to_constant(context, i) .expect("cannot convert instruction to constant for unquote operand"), Part::Constant(c) => c, Part::ConditionCode(_) => { panic!("cannot use a condition code as an unquote operand") } }; let result = self.eval_unquote_1(operator, operand); self.right_hand_sides.push(result.into()); } Action::BinaryUnquote { operator, operands } => { let a = self.right_hand_sides[operands[0].0 as usize]; let a = match a { Part::Instruction(i) => self .instr_set .instruction_to_constant(context, i) .expect("cannot convert instruction to constant for unquote operand"), Part::Constant(c) => c, Part::ConditionCode(_) => { panic!("cannot use a condition code as an unquote operand") } }; let b = self.right_hand_sides[operands[1].0 as usize]; let b = match b { Part::Instruction(i) => self .instr_set .instruction_to_constant(context, i) .expect("cannot convert instruction to constant for unquote operand"), Part::Constant(c) => c, Part::ConditionCode(_) => { panic!("cannot use a condition code as an unquote operand") } }; let result = self.eval_unquote_2(operator, a, b); self.right_hand_sides.push(result.into()); } Action::MakeIntegerConst { value, mut bit_width, } => { let value = self.peep_opt.integers.lookup(value); if bit_width.is_polymorphic() { bit_width = BitWidth::try_from( self.instr_set.instruction_result_bit_width(context, root), ) .unwrap(); } self.right_hand_sides .push(Constant::Int(value, bit_width).into()); } Action::MakeBooleanConst { value, mut bit_width, } => { if bit_width.is_polymorphic() { bit_width = BitWidth::try_from( self.instr_set.instruction_result_bit_width(context, root), ) .unwrap(); } self.right_hand_sides .push(Constant::Bool(value, bit_width).into()); } Action::MakeConditionCode { cc } => { self.right_hand_sides.push(Part::ConditionCode(cc)); } Action::MakeUnaryInst { operator, r#type: Type { kind, mut bit_width, }, operand, } => { if bit_width.is_polymorphic() { bit_width = BitWidth::try_from( self.instr_set.instruction_result_bit_width(context, root), ) .unwrap(); } let ty = Type { kind, bit_width }; let operand = self.right_hand_sides[operand.0 as usize]; let inst = self .instr_set .make_inst_1(context, root, operator, ty, operand); self.right_hand_sides.push(Part::Instruction(inst)); } Action::MakeBinaryInst { operator, r#type: Type { kind, mut bit_width, }, operands, } => { if bit_width.is_polymorphic() { bit_width = BitWidth::try_from( self.instr_set.instruction_result_bit_width(context, root), ) .unwrap(); } let ty = Type { kind, bit_width }; let a = self.right_hand_sides[operands[0].0 as usize]; let b = self.right_hand_sides[operands[1].0 as usize]; let inst = self .instr_set .make_inst_2(context, root, operator, ty, a, b); self.right_hand_sides.push(Part::Instruction(inst)); } Action::MakeTernaryInst { operator, r#type: Type { kind, mut bit_width, }, operands, } => { if bit_width.is_polymorphic() { bit_width = BitWidth::try_from( self.instr_set.instruction_result_bit_width(context, root), ) .unwrap(); } let ty = Type { kind, bit_width }; let a = self.right_hand_sides[operands[0].0 as usize]; let b = self.right_hand_sides[operands[1].0 as usize]; let c = self.right_hand_sides[operands[2].0 as usize]; let inst = self .instr_set .make_inst_3(context, root, operator, ty, a, b, c); self.right_hand_sides.push(Part::Instruction(inst)); } } } // Reuse the heap elements allocation. self.actions = actions; } fn eval_match_op( &mut self, context: &mut I::Context, root: I::Instruction, match_op: MatchOp, ) -> MatchResult { use crate::linear::MatchOp::*; log::trace!("Evaluating match operation: {:?}", match_op); let result: MatchResult = (|| match match_op { Opcode { path } => { let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; let inst = part.as_instruction().ok_or(Else)?; let op = self.instr_set.operator(context, inst).ok_or(Else)?; let op = op as u32; debug_assert!( op != 0, "`Operator` doesn't have any variant represented with zero" ); Ok(unsafe { NonZeroU32::new_unchecked(op as u32) }) } IsConst { path } => { let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; let is_const = match part { Part::Instruction(i) => { self.instr_set.instruction_to_constant(context, i).is_some() } Part::ConditionCode(_) | Part::Constant(_) => true, }; bool_to_match_result(is_const) } IsPowerOfTwo { path } => { let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; match part { Part::Constant(c) => { let is_pow2 = c.as_int().unwrap().is_power_of_two(); bool_to_match_result(is_pow2) } Part::Instruction(i) => { let c = self .instr_set .instruction_to_constant(context, i) .ok_or(Else)?; let is_pow2 = c.as_int().unwrap().is_power_of_two(); bool_to_match_result(is_pow2) } Part::ConditionCode(_) => unreachable!( "IsPowerOfTwo on a condition code" ), } } BitWidth { path } => { let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; let bit_width = match part { Part::Instruction(i) => self.instr_set.instruction_result_bit_width(context, i), Part::Constant(Constant::Int(_, w)) | Part::Constant(Constant::Bool(_, w)) => { w.fixed_width().unwrap_or_else(|| { self.instr_set.instruction_result_bit_width(context, root) }) } Part::ConditionCode(_) => panic!("BitWidth on condition code"), }; debug_assert!( bit_width != 0, "`InstructionSet` implementors must uphold the contract that \ `instruction_result_bit_width` returns one of 1, 8, 16, 32, 64, or 128" ); Ok(unsafe { NonZeroU32::new_unchecked(bit_width as u32) }) } FitsInNativeWord { path } => { let native_word_size = self.instr_set.native_word_size_in_bits(context); debug_assert!(native_word_size.is_power_of_two()); let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; let fits = match part { Part::Instruction(i) => { let size = self.instr_set.instruction_result_bit_width(context, i); size <= native_word_size } Part::Constant(c) => { let root_width = self.instr_set.instruction_result_bit_width(context, root); let size = c.bit_width(root_width); size <= native_word_size } Part::ConditionCode(_) => panic!("FitsInNativeWord on condition code"), }; bool_to_match_result(fits) } Eq { path_a, path_b } => { let path_a = self.peep_opt.paths.lookup(path_a); let part_a = self .instr_set .get_part_at_path(context, root, path_a) .ok_or(Else)?; let path_b = self.peep_opt.paths.lookup(path_b); let part_b = self .instr_set .get_part_at_path(context, root, path_b) .ok_or(Else)?; let eq = match (part_a, part_b) { (Part::Instruction(inst), Part::Constant(c1)) | (Part::Constant(c1), Part::Instruction(inst)) => { match self.instr_set.instruction_to_constant(context, inst) { Some(c2) => c1 == c2, None => false, } } (a, b) => a == b, }; bool_to_match_result(eq) } IntegerValue { path } => { let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; match part { Part::Constant(c) => { let x = c.as_int().ok_or(Else)?; let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?; Ok(id.into()) } Part::Instruction(i) => { let c = self .instr_set .instruction_to_constant(context, i) .ok_or(Else)?; let x = c.as_int().ok_or(Else)?; let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?; Ok(id.into()) } Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"), } } BooleanValue { path } => { let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; match part { Part::Constant(c) => { let b = c.as_bool().ok_or(Else)?; bool_to_match_result(b) } Part::Instruction(i) => { let c = self .instr_set .instruction_to_constant(context, i) .ok_or(Else)?; let b = c.as_bool().ok_or(Else)?; bool_to_match_result(b) } Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"), } } ConditionCode { path } => { let path = self.peep_opt.paths.lookup(path); let part = self .instr_set .get_part_at_path(context, root, path) .ok_or(Else)?; let cc = part.as_condition_code().ok_or(Else)?; let cc = cc as u32; debug_assert!(cc != 0); Ok(unsafe { NonZeroU32::new_unchecked(cc) }) } MatchOp::Nop => Err(Else), })(); log::trace!("Evaluated match operation: {:?} = {:?}", match_op, result); result } /// Attempt to apply a single peephole optimization to the given root /// instruction. /// /// If an optimization is applied, then the `root` is replaced with the /// optimization's right-hand side, and the root of the right-hand side is /// returned as `Some`. /// /// If no optimization's left-hand side matches `root`, then `root` is left /// untouched and `None` is returned. pub fn apply_one( &mut self, context: &mut I::Context, root: I::Instruction, ) -> Option { log::trace!("PeepholeOptimizer::apply_one"); self.backtracking_states.clear(); self.actions.clear(); self.right_hand_sides.clear(); let mut r#final = None; let mut query = self.peep_opt.automata.query(); loop { log::trace!("Current state: {:?}", query.current_state()); if query.is_in_final_state() { // If we're in a final state (which means an optimization is // applicable) then record that fact, but keep going. We don't // want to stop yet, because we might discover another, // more-specific optimization that is also applicable if we keep // going. And we always want to apply the most specific // optimization that matches. log::trace!("Found a match at state {:?}", query.current_state()); r#final = Some((query.current_state(), self.actions.len())); } // Anything following a `Else` transition doesn't care about the // result of this match operation, so if we partially follow the // current non-`Else` path, but don't ultimately find a matching // optimization, we want to be able to backtrack to this state and // then try taking the `Else` transition. if query.has_transition_on(&Err(Else)) { self.backtracking_states .push((query.current_state(), self.actions.len())); } let match_op = match query.current_state_data() { None => break, Some(op) => op, }; let input = self.eval_match_op(context, root, *match_op); let actions = if let Some(actions) = query.next(&input) { actions } else if r#final.is_some() { break; } else if let Some((state, actions_len)) = self.backtracking_states.pop() { query.go_to_state(state); self.actions.truncate(actions_len); query .next(&Err(Else)) .expect("backtracking states always have `Else` transitions") } else { break; }; self.actions.extend(actions.iter().copied()); } // If `final` is none, then we didn't encounter any final states, so // there are no applicable optimizations. let (final_state, actions_len) = match r#final { Some(f) => f, None => { log::trace!("No optimizations matched"); return None; } }; // Go to the last final state we saw, reset the LHS and RHS to how // they were at the time we saw the final state, and process the // final actions. self.actions.truncate(actions_len); query.go_to_state(final_state); let final_actions = query.finish().expect("should be in a final state"); self.actions.extend(final_actions.iter().copied()); self.eval_actions(context, root); // And finally, the root of the RHS for this optimization is the // last entry in `self.right_hand_sides`, so replace the old root // instruction with this one! let result = self.right_hand_sides.pop().unwrap(); let new_root = self.instr_set.replace_instruction(context, root, result); Some(new_root) } /// Keep applying peephole optimizations to the given instruction until none /// can be applied anymore. pub fn apply_all(&mut self, context: &mut I::Context, mut inst: I::Instruction) { loop { if let Some(new_inst) = self.apply_one(context, inst) { inst = new_inst; } else { break; } } } }