peepmatic_runtime/
optimizer.rs

1//! An optimizer for a set of peephole optimizations.
2
3use crate::instruction_set::InstructionSet;
4use crate::linear::{bool_to_match_result, Action, Else, MatchOp, MatchResult};
5use crate::optimizations::PeepholeOptimizations;
6use crate::part::{Constant, Part};
7use crate::r#type::{BitWidth, Type};
8use crate::unquote::UnquoteOperator;
9use peepmatic_automata::State;
10use std::convert::TryFrom;
11use std::fmt::{self, Debug};
12use std::mem;
13use std::num::NonZeroU32;
14
15/// A peephole optimizer instance that can apply a set of peephole
16/// optimizations to instructions.
17///
18/// These are created from a set of peephole optimizations with the
19/// [`PeepholeOptimizations::optimizer`] method.
20///
21/// Reusing an instance when applying peephole optimizations to different
22/// instruction sequences means that you reuse internal allocations that are
23/// used to match left-hand sides and build up right-hand sides.
24pub struct PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
25where
26    TInstructionSet: InstructionSet<'ctx>,
27{
28    pub(crate) peep_opt: &'peep PeepholeOptimizations<TInstructionSet::Operator>,
29    pub(crate) instr_set: TInstructionSet,
30    pub(crate) left_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
31    pub(crate) right_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
32    pub(crate) actions: Vec<Action<TInstructionSet::Operator>>,
33    pub(crate) backtracking_states: Vec<(State, usize, usize)>,
34}
35
36impl<'peep, 'ctx, TInstructionSet> Debug for PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
37where
38    TInstructionSet: InstructionSet<'ctx>,
39{
40    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41        let PeepholeOptimizer {
42            peep_opt,
43            instr_set: _,
44            left_hand_sides,
45            right_hand_sides,
46            actions,
47            backtracking_states,
48        } = self;
49        f.debug_struct("PeepholeOptimizer")
50            .field("peep_opt", peep_opt)
51            .field("instr_set", &"_")
52            .field("left_hand_sides", left_hand_sides)
53            .field("right_hand_sides", right_hand_sides)
54            .field("actions", actions)
55            .field("backtracking_states", backtracking_states)
56            .finish()
57    }
58}
59
60impl<'peep, 'ctx, TInstructionSet> PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
61where
62    TInstructionSet: InstructionSet<'ctx>,
63{
64    fn eval_unquote_1(&self, operator: UnquoteOperator, a: Constant) -> Constant {
65        use Constant::*;
66
67        macro_rules! map_int {
68            ( $c:expr , | $x:ident | $e:expr ) => {
69                match $c {
70                    Int($x, w) => Int($e, w),
71                    Bool(..) => panic!("not an integer"),
72                }
73            };
74        }
75
76        match operator {
77            UnquoteOperator::Log2 => map_int!(a, |x| x.trailing_zeros() as _),
78            UnquoteOperator::Neg => map_int!(a, |x| x.wrapping_neg()),
79            UnquoteOperator::Band
80            | UnquoteOperator::Bor
81            | UnquoteOperator::Bxor
82            | UnquoteOperator::Iadd
83            | UnquoteOperator::Imul
84            | UnquoteOperator::Isub => unreachable!("not a unary unquote operator: {:?}", operator),
85        }
86    }
87
88    fn eval_unquote_2(&self, operator: UnquoteOperator, a: Constant, b: Constant) -> Constant {
89        use Constant::*;
90
91        macro_rules! fold_ints {
92            ( $c1:expr , $c2:expr , | $x:ident , $y:ident | $e:expr ) => {
93                match ($c1, $c2) {
94                    (Int($x, w1), Int($y, w2)) if w1 == w2 => Int($e, w1),
95                    _ => panic!("not two integers of the same width"),
96                }
97            };
98        }
99
100        match operator {
101            UnquoteOperator::Band => fold_ints!(a, b, |x, y| x & y),
102            UnquoteOperator::Bor => fold_ints!(a, b, |x, y| x | y),
103            UnquoteOperator::Bxor => fold_ints!(a, b, |x, y| x ^ y),
104            UnquoteOperator::Iadd => fold_ints!(a, b, |x, y| x.wrapping_add(y)),
105            UnquoteOperator::Imul => fold_ints!(a, b, |x, y| x.wrapping_mul(y)),
106            UnquoteOperator::Isub => fold_ints!(a, b, |x, y| x.wrapping_sub(y)),
107            UnquoteOperator::Log2 | UnquoteOperator::Neg => {
108                unreachable!("not a binary unquote operator: {:?}", operator)
109            }
110        }
111    }
112
113    fn eval_actions(
114        &mut self,
115        context: &mut TInstructionSet::Context,
116        root: TInstructionSet::Instruction,
117    ) {
118        let mut actions = mem::replace(&mut self.actions, vec![]);
119
120        for action in actions.drain(..) {
121            log::trace!("Evaluating action: {:?}", action);
122            match action {
123                Action::GetLhs { lhs } => {
124                    let lhs = self.left_hand_sides[lhs.0 as usize];
125                    self.right_hand_sides.push(lhs);
126                }
127                Action::UnaryUnquote { operator, operand } => {
128                    let operand = self.right_hand_sides[operand.0 as usize];
129                    let operand = match operand {
130                        Part::Instruction(i) => self
131                            .instr_set
132                            .instruction_to_constant(context, i)
133                            .expect("cannot convert instruction to constant for unquote operand"),
134                        Part::Constant(c) => c,
135                        Part::ConditionCode(_) => {
136                            panic!("cannot use a condition code as an unquote operand")
137                        }
138                    };
139                    let result = self.eval_unquote_1(operator, operand);
140                    self.right_hand_sides.push(result.into());
141                }
142                Action::BinaryUnquote { operator, operands } => {
143                    let a = self.right_hand_sides[operands[0].0 as usize];
144                    let a = match a {
145                        Part::Instruction(i) => self
146                            .instr_set
147                            .instruction_to_constant(context, i)
148                            .expect("cannot convert instruction to constant for unquote operand"),
149                        Part::Constant(c) => c,
150                        Part::ConditionCode(_) => {
151                            panic!("cannot use a condition code as an unquote operand")
152                        }
153                    };
154
155                    let b = self.right_hand_sides[operands[1].0 as usize];
156                    let b = match b {
157                        Part::Instruction(i) => self
158                            .instr_set
159                            .instruction_to_constant(context, i)
160                            .expect("cannot convert instruction to constant for unquote operand"),
161                        Part::Constant(c) => c,
162                        Part::ConditionCode(_) => {
163                            panic!("cannot use a condition code as an unquote operand")
164                        }
165                    };
166
167                    let result = self.eval_unquote_2(operator, a, b);
168                    self.right_hand_sides.push(result.into());
169                }
170                Action::MakeIntegerConst {
171                    value,
172                    mut bit_width,
173                } => {
174                    let value = self.peep_opt.integers.lookup(value);
175                    if bit_width.is_polymorphic() {
176                        bit_width = BitWidth::try_from(
177                            self.instr_set.instruction_result_bit_width(context, root),
178                        )
179                        .unwrap();
180                    }
181                    self.right_hand_sides
182                        .push(Constant::Int(value, bit_width).into());
183                }
184                Action::MakeBooleanConst {
185                    value,
186                    mut bit_width,
187                } => {
188                    if bit_width.is_polymorphic() {
189                        bit_width = BitWidth::try_from(
190                            self.instr_set.instruction_result_bit_width(context, root),
191                        )
192                        .unwrap();
193                    }
194                    self.right_hand_sides
195                        .push(Constant::Bool(value, bit_width).into());
196                }
197                Action::MakeConditionCode { cc } => {
198                    self.right_hand_sides.push(Part::ConditionCode(cc));
199                }
200                Action::MakeUnaryInst {
201                    operator,
202                    r#type:
203                        Type {
204                            kind,
205                            mut bit_width,
206                        },
207                    operand,
208                } => {
209                    if bit_width.is_polymorphic() {
210                        bit_width = BitWidth::try_from(
211                            self.instr_set.instruction_result_bit_width(context, root),
212                        )
213                        .unwrap();
214                    }
215                    let ty = Type { kind, bit_width };
216                    let operand = self.right_hand_sides[operand.0 as usize];
217                    let inst = self
218                        .instr_set
219                        .make_inst_1(context, root, operator, ty, operand);
220                    self.right_hand_sides.push(Part::Instruction(inst));
221                }
222                Action::MakeBinaryInst {
223                    operator,
224                    r#type:
225                        Type {
226                            kind,
227                            mut bit_width,
228                        },
229                    operands,
230                } => {
231                    if bit_width.is_polymorphic() {
232                        bit_width = BitWidth::try_from(
233                            self.instr_set.instruction_result_bit_width(context, root),
234                        )
235                        .unwrap();
236                    }
237                    let ty = Type { kind, bit_width };
238                    let a = self.right_hand_sides[operands[0].0 as usize];
239                    let b = self.right_hand_sides[operands[1].0 as usize];
240                    let inst = self
241                        .instr_set
242                        .make_inst_2(context, root, operator, ty, a, b);
243                    self.right_hand_sides.push(Part::Instruction(inst));
244                }
245                Action::MakeTernaryInst {
246                    operator,
247                    r#type:
248                        Type {
249                            kind,
250                            mut bit_width,
251                        },
252                    operands,
253                } => {
254                    if bit_width.is_polymorphic() {
255                        bit_width = BitWidth::try_from(
256                            self.instr_set.instruction_result_bit_width(context, root),
257                        )
258                        .unwrap();
259                    }
260                    let ty = Type { kind, bit_width };
261                    let a = self.right_hand_sides[operands[0].0 as usize];
262                    let b = self.right_hand_sides[operands[1].0 as usize];
263                    let c = self.right_hand_sides[operands[2].0 as usize];
264                    let inst = self
265                        .instr_set
266                        .make_inst_3(context, root, operator, ty, a, b, c);
267                    self.right_hand_sides.push(Part::Instruction(inst));
268                }
269            }
270        }
271
272        // Reuse the heap elements allocation.
273        self.actions = actions;
274    }
275
276    fn eval_match_op(
277        &mut self,
278        context: &mut TInstructionSet::Context,
279        root: TInstructionSet::Instruction,
280        match_op: MatchOp,
281    ) -> MatchResult {
282        use crate::linear::MatchOp::*;
283
284        log::trace!("Evaluating match operation: {:?}", match_op);
285        let result: MatchResult = (|| match match_op {
286            Opcode(id) => {
287                let part = self.left_hand_sides[id.0 as usize];
288                let inst = part.as_instruction().ok_or(Else)?;
289                let op = self
290                    .instr_set
291                    .operator(context, inst, &mut self.left_hand_sides)
292                    .ok_or(Else)?;
293                Ok(op.into())
294            }
295            IsConst(id) => {
296                let part = self.left_hand_sides[id.0 as usize];
297                let is_const = match part {
298                    Part::Instruction(i) => {
299                        self.instr_set.instruction_to_constant(context, i).is_some()
300                    }
301                    Part::ConditionCode(_) | Part::Constant(_) => true,
302                };
303                bool_to_match_result(is_const)
304            }
305            IsPowerOfTwo(id) => {
306                let part = self.left_hand_sides[id.0 as usize];
307                match part {
308                    Part::Constant(c) => {
309                        let is_pow2 = c.as_int().unwrap().is_power_of_two();
310                        bool_to_match_result(is_pow2)
311                    }
312                    Part::Instruction(i) => {
313                        let c = self
314                            .instr_set
315                            .instruction_to_constant(context, i)
316                            .ok_or(Else)?;
317                        let is_pow2 = c.as_int().unwrap().is_power_of_two();
318                        bool_to_match_result(is_pow2)
319                    }
320                    Part::ConditionCode(_) => unreachable!("IsPowerOfTwo on a condition code"),
321                }
322            }
323            BitWidth(id) => {
324                let part = self.left_hand_sides[id.0 as usize];
325                let bit_width = match part {
326                    Part::Instruction(i) => self.instr_set.instruction_result_bit_width(context, i),
327                    Part::Constant(Constant::Int(_, w)) | Part::Constant(Constant::Bool(_, w)) => {
328                        w.fixed_width().unwrap_or_else(|| {
329                            self.instr_set.instruction_result_bit_width(context, root)
330                        })
331                    }
332                    Part::ConditionCode(_) => panic!("BitWidth on condition code"),
333                };
334                debug_assert!(
335                    bit_width != 0,
336                    "`InstructionSet` implementors must uphold the contract that \
337                     `instruction_result_bit_width` returns one of 1, 8, 16, 32, 64, or 128"
338                );
339                Ok(unsafe { NonZeroU32::new_unchecked(bit_width as u32) })
340            }
341            FitsInNativeWord(id) => {
342                let native_word_size = self.instr_set.native_word_size_in_bits(context);
343                debug_assert!(native_word_size.is_power_of_two());
344
345                let part = self.left_hand_sides[id.0 as usize];
346                let fits = match part {
347                    Part::Instruction(i) => {
348                        let size = self.instr_set.instruction_result_bit_width(context, i);
349                        size <= native_word_size
350                    }
351                    Part::Constant(c) => {
352                        let root_width = self.instr_set.instruction_result_bit_width(context, root);
353                        let size = c.bit_width(root_width);
354                        size <= native_word_size
355                    }
356                    Part::ConditionCode(_) => panic!("FitsInNativeWord on condition code"),
357                };
358                bool_to_match_result(fits)
359            }
360            Eq(a, b) => {
361                let part_a = self.left_hand_sides[a.0 as usize];
362                let part_b = self.left_hand_sides[b.0 as usize];
363                let eq = match (part_a, part_b) {
364                    (Part::Instruction(inst), Part::Constant(c1))
365                    | (Part::Constant(c1), Part::Instruction(inst)) => {
366                        match self.instr_set.instruction_to_constant(context, inst) {
367                            Some(c2) => c1 == c2,
368                            None => false,
369                        }
370                    }
371                    (a, b) => a == b,
372                };
373                bool_to_match_result(eq)
374            }
375            IntegerValue(id) => {
376                let part = self.left_hand_sides[id.0 as usize];
377                match part {
378                    Part::Constant(c) => {
379                        let x = c.as_int().ok_or(Else)?;
380                        let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?;
381                        Ok(id.into())
382                    }
383                    Part::Instruction(i) => {
384                        let c = self
385                            .instr_set
386                            .instruction_to_constant(context, i)
387                            .ok_or(Else)?;
388                        let x = c.as_int().ok_or(Else)?;
389                        let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?;
390                        Ok(id.into())
391                    }
392                    Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
393                }
394            }
395            BooleanValue(id) => {
396                let part = self.left_hand_sides[id.0 as usize];
397                match part {
398                    Part::Constant(c) => {
399                        let b = c.as_bool().ok_or(Else)?;
400                        bool_to_match_result(b)
401                    }
402                    Part::Instruction(i) => {
403                        let c = self
404                            .instr_set
405                            .instruction_to_constant(context, i)
406                            .ok_or(Else)?;
407                        let b = c.as_bool().ok_or(Else)?;
408                        bool_to_match_result(b)
409                    }
410                    Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
411                }
412            }
413            ConditionCode(id) => {
414                let part = self.left_hand_sides[id.0 as usize];
415                let cc = part.as_condition_code().ok_or(Else)?;
416                let cc = cc as u32;
417                debug_assert!(cc != 0);
418                Ok(unsafe { NonZeroU32::new_unchecked(cc) })
419            }
420            MatchOp::Nop => Err(Else),
421        })();
422        log::trace!("Evaluated match operation: {:?} = {:?}", match_op, result);
423        result
424    }
425
426    /// Attempt to apply a single peephole optimization to the given root
427    /// instruction.
428    ///
429    /// If an optimization is applied, then the `root` is replaced with the
430    /// optimization's right-hand side, and the root of the right-hand side is
431    /// returned as `Some`.
432    ///
433    /// If no optimization's left-hand side matches `root`, then `root` is left
434    /// untouched and `None` is returned.
435    pub fn apply_one(
436        &mut self,
437        context: &mut TInstructionSet::Context,
438        root: TInstructionSet::Instruction,
439    ) -> Option<TInstructionSet::Instruction> {
440        log::trace!("PeepholeOptimizer::apply_one");
441
442        self.backtracking_states.clear();
443        self.actions.clear();
444        self.right_hand_sides.clear();
445        self.left_hand_sides.clear();
446
447        // `LhsId(0)` is always the root.
448        self.left_hand_sides.push(Part::Instruction(root));
449
450        let mut r#final = None;
451
452        let mut query = self.peep_opt.automata.query();
453        loop {
454            log::trace!("Current state: {:?}", query.current_state());
455            log::trace!(
456                "self.left_hand_sides = {:#?}",
457                self.left_hand_sides.iter().enumerate().collect::<Vec<_>>()
458            );
459
460            if query.is_in_final_state() {
461                // If we're in a final state (which means an optimization is
462                // applicable) then record that fact, but keep going. We don't
463                // want to stop yet, because we might discover another,
464                // more-specific optimization that is also applicable if we keep
465                // going. And we always want to apply the most specific
466                // optimization that matches.
467                log::trace!("Found a match at state {:?}", query.current_state());
468                r#final = Some((query.current_state(), self.actions.len()));
469            }
470
471            // Anything following a `Else` transition doesn't care about the
472            // result of this match operation, so if we partially follow the
473            // current non-`Else` path, but don't ultimately find a matching
474            // optimization, we want to be able to backtrack to this state and
475            // then try taking the `Else` transition.
476            if query.has_transition_on(&Err(Else)) {
477                self.backtracking_states.push((
478                    query.current_state(),
479                    self.actions.len(),
480                    self.left_hand_sides.len(),
481                ));
482            }
483
484            let match_op = match query.current_state_data() {
485                None => break,
486                Some(op) => op,
487            };
488
489            let input = self.eval_match_op(context, root, *match_op);
490
491            let actions = if let Some(actions) = query.next(&input) {
492                actions
493            } else if r#final.is_some() {
494                break;
495            } else if let Some((state, actions_len, lhs_len)) = self.backtracking_states.pop() {
496                query.go_to_state(state);
497                self.actions.truncate(actions_len);
498                self.left_hand_sides.truncate(lhs_len);
499                query
500                    .next(&Err(Else))
501                    .expect("backtracking states always have `Else` transitions")
502            } else {
503                break;
504            };
505
506            self.actions.extend(actions.iter().copied());
507        }
508
509        // If `final` is none, then we didn't encounter any final states, so
510        // there are no applicable optimizations.
511        let (final_state, actions_len) = match r#final {
512            Some(f) => f,
513            None => {
514                log::trace!("No optimizations matched");
515                return None;
516            }
517        };
518
519        // Go to the last final state we saw, reset the LHS and RHS to how
520        // they were at the time we saw the final state, and process the
521        // final actions.
522        self.actions.truncate(actions_len);
523        query.go_to_state(final_state);
524        let final_actions = query.finish().expect("should be in a final state");
525        self.actions.extend(final_actions.iter().copied());
526        self.eval_actions(context, root);
527
528        // And finally, the root of the RHS for this optimization is the
529        // last entry in `self.right_hand_sides`, so replace the old root
530        // instruction with this one!
531        let result = self.right_hand_sides.pop().unwrap();
532        let new_root = self.instr_set.replace_instruction(context, root, result);
533        Some(new_root)
534    }
535
536    /// Keep applying peephole optimizations to the given instruction until none
537    /// can be applied anymore.
538    pub fn apply_all(
539        &mut self,
540        context: &mut TInstructionSet::Context,
541        mut inst: TInstructionSet::Instruction,
542    ) {
543        loop {
544            if let Some(new_inst) = self.apply_one(context, inst) {
545                inst = new_inst;
546            } else {
547                break;
548            }
549        }
550    }
551}