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}