peepmatic_runtime/
linear.rs

1//! A linear IR for optimizations.
2//!
3//! This IR is designed such that it should be easy to combine multiple linear
4//! optimizations into a single automata.
5//!
6//! See also `src/linearize.rs` for the AST to linear IR translation pass.
7
8use crate::cc::ConditionCode;
9use crate::integer_interner::{IntegerId, IntegerInterner};
10use crate::r#type::{BitWidth, Type};
11use crate::unquote::UnquoteOperator;
12use serde::{Deserialize, Serialize};
13use std::fmt::Debug;
14use std::hash::Hash;
15use std::num::NonZeroU32;
16
17/// A set of linear optimizations.
18#[derive(Debug)]
19pub struct Optimizations<TOperator>
20where
21    TOperator: 'static + Copy + Debug + Eq + Hash,
22{
23    /// The linear optimizations.
24    pub optimizations: Vec<Optimization<TOperator>>,
25
26    /// The integer literals referenced by these optimizations.
27    pub integers: IntegerInterner,
28}
29
30/// A linearized optimization.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct Optimization<TOperator>
33where
34    TOperator: 'static + Copy + Debug + Eq + Hash,
35{
36    /// The chain of match operations and expected results for this
37    /// optimization.
38    pub matches: Vec<Match>,
39
40    /// Actions to perform, given that the operation resulted in the expected
41    /// value.
42    pub actions: Vec<Action<TOperator>>,
43}
44
45/// Match any value.
46///
47/// This can be used to create fallback, wildcard-style transitions between
48/// states.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
50pub struct Else;
51
52/// The result of evaluating a `MatchOp`.
53///
54/// This is either a specific non-zero `u32`, or a fallback that matches
55/// everything.
56pub type MatchResult = Result<NonZeroU32, Else>;
57
58/// Convert a boolean to a `MatchResult`.
59#[inline]
60pub fn bool_to_match_result(b: bool) -> MatchResult {
61    let b = b as u32;
62    unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) }
63}
64
65/// A partial match of an optimization's LHS.
66///
67/// An match is composed of a matching operation and the expected result of that
68/// operation. Each match will basically become a state and a transition edge
69/// out of that state in the final automata.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct Match {
72    /// The matching operation to perform.
73    pub operation: MatchOp,
74
75    /// The expected result of our matching operation, that enables us to
76    /// continue to the next match, or `Else` for "don't care" wildcard-style
77    /// matching.
78    pub expected: MatchResult,
79}
80
81/// A matching operation to be performed on some Cranelift instruction as part
82/// of determining whether an optimization is applicable.
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
84pub enum MatchOp {
85    /// Switch on the opcode of an instruction.
86    ///
87    /// Upon successfully matching an instruction's opcode, bind each of its
88    /// operands to a LHS temporary.
89    Opcode(LhsId),
90
91    /// Does an instruction have a constant value?
92    IsConst(LhsId),
93
94    /// Is the constant value a power of two?
95    IsPowerOfTwo(LhsId),
96
97    /// Switch on the bit width of a value.
98    BitWidth(LhsId),
99
100    /// Does the value fit in our target architecture's native word size?
101    FitsInNativeWord(LhsId),
102
103    /// Are the instructions (or immediates) the same?
104    Eq(LhsId, LhsId),
105
106    /// Switch on the constant integer value of an instruction.
107    IntegerValue(LhsId),
108
109    /// Switch on the constant boolean value of an instruction.
110    BooleanValue(LhsId),
111
112    /// Switch on a condition code.
113    ConditionCode(LhsId),
114
115    /// No operation. Always evaluates to `Else`.
116    ///
117    /// Never appears in real optimizations; nonetheless required to support
118    /// corner cases of the DSL, such as a LHS pattern that is nothing but a
119    /// variable.
120    Nop,
121}
122
123/// A canonicalized identifier for a left-hand side value that was bound in a
124/// pattern.
125///
126/// These are defined in a pre-order traversal of the LHS pattern by successful
127/// `MatchOp::Opcode` matches.
128#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
129pub struct LhsId(pub u16);
130
131/// A canonicalized identifier for a right-hand side value.
132///
133/// These are defined by RHS actions.
134#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
135pub struct RhsId(pub u16);
136
137/// An action to perform when transitioning between states in the automata.
138///
139/// When evaluating actions, the `i^th` action implicitly defines the
140/// `RhsId(i)`.
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
142pub enum Action<TOperator> {
143    /// Reuse something from the left-hand side.
144    GetLhs {
145        /// The left-hand side instruction or value.
146        lhs: LhsId,
147    },
148
149    /// Perform compile-time evaluation.
150    UnaryUnquote {
151        /// The unquote operator.
152        operator: UnquoteOperator,
153        /// The constant operand to this unquote.
154        operand: RhsId,
155    },
156
157    /// Perform compile-time evaluation.
158    BinaryUnquote {
159        /// The unquote operator.
160        operator: UnquoteOperator,
161        /// The constant operands to this unquote.
162        operands: [RhsId; 2],
163    },
164
165    /// Create an integer constant.
166    MakeIntegerConst {
167        /// The constant integer value.
168        value: IntegerId,
169        /// The bit width of this constant.
170        bit_width: BitWidth,
171    },
172
173    /// Create a boolean constant.
174    MakeBooleanConst {
175        /// The constant boolean value.
176        value: bool,
177        /// The bit width of this constant.
178        bit_width: BitWidth,
179    },
180
181    /// Create a condition code.
182    MakeConditionCode {
183        /// The condition code.
184        cc: ConditionCode,
185    },
186
187    /// Make a unary instruction.
188    MakeUnaryInst {
189        /// The operand for this instruction.
190        operand: RhsId,
191        /// The type of this instruction's result.
192        r#type: Type,
193        /// The operator for this instruction.
194        operator: TOperator,
195    },
196
197    /// Make a binary instruction.
198    MakeBinaryInst {
199        /// The opcode for this instruction.
200        operator: TOperator,
201        /// The type of this instruction's result.
202        r#type: Type,
203        /// The operands for this instruction.
204        operands: [RhsId; 2],
205    },
206
207    /// Make a ternary instruction.
208    MakeTernaryInst {
209        /// The opcode for this instruction.
210        operator: TOperator,
211        /// The type of this instruction's result.
212        r#type: Type,
213        /// The operands for this instruction.
214        operands: [RhsId; 3],
215    },
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use peepmatic_test_operator::TestOperator;
222
223    // These types all end up in the automaton, so we should take care that they
224    // are small and don't fill up the data cache (or take up too much
225    // serialized size).
226
227    #[test]
228    fn match_result_size() {
229        assert_eq!(std::mem::size_of::<MatchResult>(), 4);
230    }
231
232    #[test]
233    fn match_op_size() {
234        assert_eq!(std::mem::size_of::<MatchOp>(), 6);
235    }
236
237    #[test]
238    fn action_size() {
239        assert_eq!(std::mem::size_of::<Action<TestOperator>>(), 16);
240    }
241}