1 //! Optimization driver using ISLE rewrite rules on an egraph.
2 
3 use crate::egraph::{NewOrExistingInst, OptimizeCtx};
4 pub use crate::ir::condcodes::{FloatCC, IntCC};
5 use crate::ir::dfg::ValueDef;
6 pub use crate::ir::immediates::{Ieee128, Ieee16, Ieee32, Ieee64, Imm64, Offset32, Uimm8, V128Imm};
7 use crate::ir::instructions::InstructionFormat;
8 pub use crate::ir::types::*;
9 pub use crate::ir::{
10     AtomicRmwOp, BlockCall, Constant, DynamicStackSlot, FuncRef, GlobalValue, Immediate,
11     InstructionData, MemFlags, Opcode, StackSlot, TrapCode, Type, Value,
12 };
13 use crate::isle_common_prelude_methods;
14 use crate::machinst::isle::*;
15 use crate::trace;
16 use cranelift_entity::packed_option::ReservedValue;
17 use smallvec::{smallvec, SmallVec};
18 use std::marker::PhantomData;
19 
20 #[allow(dead_code)]
21 pub type Unit = ();
22 pub type Range = (usize, usize);
23 pub type ValueArray2 = [Value; 2];
24 pub type ValueArray3 = [Value; 3];
25 
26 const MAX_ISLE_RETURNS: usize = 8;
27 
28 pub type ConstructorVec<T> = SmallVec<[T; MAX_ISLE_RETURNS]>;
29 
30 type TypeAndInstructionData = (Type, InstructionData);
31 
32 impl<T: smallvec::Array> generated_code::Length for SmallVec<T> {
33     #[inline]
34     fn len(&self) -> usize {
35         SmallVec::len(self)
36     }
37 }
38 
39 pub(crate) mod generated_code;
40 use generated_code::{ContextIter, IntoContextIter};
41 
42 pub(crate) struct IsleContext<'a, 'b, 'c> {
43     pub(crate) ctx: &'a mut OptimizeCtx<'b, 'c>,
44 }
45 
46 pub(crate) struct InstDataEtorIter<'a, 'b, 'c> {
47     stack: SmallVec<[Value; 8]>,
48     _phantom1: PhantomData<&'a ()>,
49     _phantom2: PhantomData<&'b ()>,
50     _phantom3: PhantomData<&'c ()>,
51 }
52 
53 impl Default for InstDataEtorIter<'_, '_, '_> {
54     fn default() -> Self {
55         InstDataEtorIter {
56             stack: SmallVec::default(),
57             _phantom1: PhantomData,
58             _phantom2: PhantomData,
59             _phantom3: PhantomData,
60         }
61     }
62 }
63 
64 impl<'a, 'b, 'c> InstDataEtorIter<'a, 'b, 'c> {
65     fn new(root: Value) -> Self {
66         debug_assert_ne!(root, Value::reserved_value());
67         trace!("new iter from root {root}");
68         Self {
69             stack: smallvec![root],
70             _phantom1: PhantomData,
71             _phantom2: PhantomData,
72             _phantom3: PhantomData,
73         }
74     }
75 }
76 
77 impl<'a, 'b, 'c> ContextIter for InstDataEtorIter<'a, 'b, 'c>
78 where
79     'b: 'a,
80     'c: 'b,
81 {
82     type Context = IsleContext<'a, 'b, 'c>;
83     type Output = (Type, InstructionData);
84 
85     fn next(&mut self, ctx: &mut IsleContext<'a, 'b, 'c>) -> Option<Self::Output> {
86         while let Some(value) = self.stack.pop() {
87             debug_assert!(ctx.ctx.func.dfg.value_is_real(value));
88             trace!("iter: value {:?}", value);
89             match ctx.ctx.func.dfg.value_def(value) {
90                 ValueDef::Union(x, y) => {
91                     debug_assert_ne!(x, Value::reserved_value());
92                     debug_assert_ne!(y, Value::reserved_value());
93                     trace!(" -> {}, {}", x, y);
94                     self.stack.push(x);
95                     self.stack.push(y);
96                     continue;
97                 }
98                 ValueDef::Result(inst, _) if ctx.ctx.func.dfg.inst_results(inst).len() == 1 => {
99                     let ty = ctx.ctx.func.dfg.value_type(value);
100                     trace!(" -> value of type {}", ty);
101                     return Some((ty, ctx.ctx.func.dfg.insts[inst]));
102                 }
103                 _ => {}
104             }
105         }
106         None
107     }
108 }
109 
110 impl<'a, 'b, 'c> IntoContextIter for InstDataEtorIter<'a, 'b, 'c>
111 where
112     'b: 'a,
113     'c: 'b,
114 {
115     type Context = IsleContext<'a, 'b, 'c>;
116     type Output = (Type, InstructionData);
117     type IntoIter = Self;
118 
119     fn into_context_iter(self) -> Self {
120         self
121     }
122 }
123 
124 #[derive(Default)]
125 pub(crate) struct MaybeUnaryEtorIter<'a, 'b, 'c> {
126     opcode: Option<Opcode>,
127     inner: InstDataEtorIter<'a, 'b, 'c>,
128     fallback: Option<Value>,
129 }
130 
131 impl MaybeUnaryEtorIter<'_, '_, '_> {
132     fn new(opcode: Opcode, value: Value) -> Self {
133         debug_assert_eq!(opcode.format(), InstructionFormat::Unary);
134         Self {
135             opcode: Some(opcode),
136             inner: InstDataEtorIter::new(value),
137             fallback: Some(value),
138         }
139     }
140 }
141 
142 impl<'a, 'b, 'c> ContextIter for MaybeUnaryEtorIter<'a, 'b, 'c>
143 where
144     'b: 'a,
145     'c: 'b,
146 {
147     type Context = IsleContext<'a, 'b, 'c>;
148     type Output = (Type, Value);
149 
150     fn next(&mut self, ctx: &mut IsleContext<'a, 'b, 'c>) -> Option<Self::Output> {
151         debug_assert_ne!(self.opcode, None);
152         while let Some((ty, inst_def)) = self.inner.next(ctx) {
153             let InstructionData::Unary { opcode, arg } = inst_def else {
154                 continue;
155             };
156             if Some(opcode) == self.opcode {
157                 self.fallback = None;
158                 return Some((ty, arg));
159             }
160         }
161 
162         self.fallback.take().map(|value| {
163             let ty = generated_code::Context::value_type(ctx, value);
164             (ty, value)
165         })
166     }
167 }
168 
169 impl<'a, 'b, 'c> IntoContextIter for MaybeUnaryEtorIter<'a, 'b, 'c>
170 where
171     'b: 'a,
172     'c: 'b,
173 {
174     type Context = IsleContext<'a, 'b, 'c>;
175     type Output = (Type, Value);
176     type IntoIter = Self;
177 
178     fn into_context_iter(self) -> Self {
179         self
180     }
181 }
182 
183 impl<'a, 'b, 'c> generated_code::Context for IsleContext<'a, 'b, 'c> {
184     isle_common_prelude_methods!();
185 
186     type inst_data_etor_returns = InstDataEtorIter<'a, 'b, 'c>;
187 
188     fn inst_data_etor(&mut self, eclass: Value, returns: &mut InstDataEtorIter<'a, 'b, 'c>) {
189         *returns = InstDataEtorIter::new(eclass);
190     }
191 
192     type inst_data_tupled_etor_returns = InstDataEtorIter<'a, 'b, 'c>;
193 
194     fn inst_data_tupled_etor(&mut self, eclass: Value, returns: &mut InstDataEtorIter<'a, 'b, 'c>) {
195         // Literally identical to `inst_data_etor`, just a different nominal type in ISLE
196         self.inst_data_etor(eclass, returns);
197     }
198 
199     fn make_inst_ctor(&mut self, ty: Type, op: &InstructionData) -> Value {
200         trace!("make_inst_ctor: creating {:?}", op);
201         let value = self.ctx.insert_pure_enode(NewOrExistingInst::New(*op, ty));
202         trace!("make_inst_ctor: {:?} -> {}", op, value);
203         value
204     }
205 
206     fn value_array_2_ctor(&mut self, arg0: Value, arg1: Value) -> ValueArray2 {
207         [arg0, arg1]
208     }
209 
210     fn value_array_3_ctor(&mut self, arg0: Value, arg1: Value, arg2: Value) -> ValueArray3 {
211         [arg0, arg1, arg2]
212     }
213 
214     #[inline]
215     fn value_type(&mut self, val: Value) -> Type {
216         self.ctx.func.dfg.value_type(val)
217     }
218 
219     fn iconst_sextend_etor(
220         &mut self,
221         (ty, inst_data): (Type, InstructionData),
222     ) -> Option<(Type, i64)> {
223         if let InstructionData::UnaryImm {
224             opcode: Opcode::Iconst,
225             imm,
226         } = inst_data
227         {
228             Some((ty, self.i64_sextend_imm64(ty, imm)))
229         } else {
230             None
231         }
232     }
233 
234     fn remat(&mut self, value: Value) -> Value {
235         trace!("remat: {}", value);
236         self.ctx.remat_values.insert(value);
237         self.ctx.stats.remat += 1;
238         value
239     }
240 
241     fn subsume(&mut self, value: Value) -> Value {
242         trace!("subsume: {}", value);
243         self.ctx.subsume_values.insert(value);
244         self.ctx.stats.subsume += 1;
245         value
246     }
247 
248     fn splat64(&mut self, val: u64) -> Constant {
249         let val = u128::from(val);
250         let val = val | (val << 64);
251         let imm = V128Imm(val.to_le_bytes());
252         self.ctx.func.dfg.constants.insert(imm.into())
253     }
254 
255     type sextend_maybe_etor_returns = MaybeUnaryEtorIter<'a, 'b, 'c>;
256     fn sextend_maybe_etor(&mut self, value: Value, returns: &mut Self::sextend_maybe_etor_returns) {
257         *returns = MaybeUnaryEtorIter::new(Opcode::Sextend, value);
258     }
259 
260     type uextend_maybe_etor_returns = MaybeUnaryEtorIter<'a, 'b, 'c>;
261     fn uextend_maybe_etor(&mut self, value: Value, returns: &mut Self::uextend_maybe_etor_returns) {
262         *returns = MaybeUnaryEtorIter::new(Opcode::Uextend, value);
263     }
264 
265     // NB: Cranelift's defined semantics for `fcvt_from_{s,u}int` match Rust's
266     // own semantics for converting an integer to a float, so these are all
267     // implemented with `as` conversions in Rust.
268     fn f32_from_uint(&mut self, n: u64) -> Ieee32 {
269         Ieee32::with_float(n as f32)
270     }
271 
272     fn f64_from_uint(&mut self, n: u64) -> Ieee64 {
273         Ieee64::with_float(n as f64)
274     }
275 
276     fn f32_from_sint(&mut self, n: i64) -> Ieee32 {
277         Ieee32::with_float(n as f32)
278     }
279 
280     fn f64_from_sint(&mut self, n: i64) -> Ieee64 {
281         Ieee64::with_float(n as f64)
282     }
283 
284     fn u64_bswap16(&mut self, n: u64) -> u64 {
285         (n as u16).swap_bytes() as u64
286     }
287 
288     fn u64_bswap32(&mut self, n: u64) -> u64 {
289         (n as u32).swap_bytes() as u64
290     }
291 
292     fn u64_bswap64(&mut self, n: u64) -> u64 {
293         n.swap_bytes()
294     }
295 
296     fn ieee128_constant_extractor(&mut self, n: Constant) -> Option<Ieee128> {
297         self.ctx.func.dfg.constants.get(n).try_into().ok()
298     }
299 
300     fn ieee128_constant(&mut self, n: Ieee128) -> Constant {
301         self.ctx.func.dfg.constants.insert(n.into())
302     }
303 }
304