1 //! Cost functions for egraph representation.
2 
3 use crate::ir::Opcode;
4 
5 /// A cost of computing some value in the program.
6 ///
7 /// Costs are measured in an arbitrary union that we represent in a
8 /// `u32`. The ordering is meant to be meaningful, but the value of a
9 /// single unit is arbitrary (and "not to scale"). We use a collection
10 /// of heuristics to try to make this approximation at least usable.
11 ///
12 /// We start by defining costs for each opcode (see `pure_op_cost`
13 /// below). The cost of computing some value, initially, is the cost
14 /// of its opcode, plus the cost of computing its inputs.
15 ///
16 /// We then adjust the cost according to loop nests: for each
17 /// loop-nest level, we multiply by 1024. Because we only have 32
18 /// bits, we limit this scaling to a loop-level of two (i.e., multiply
19 /// by 2^20 ~= 1M).
20 ///
21 /// Arithmetic on costs is always saturating: we don't want to wrap
22 /// around and return to a tiny cost when adding the costs of two very
23 /// expensive operations. It is better to approximate and lose some
24 /// precision than to lose the ordering by wrapping.
25 ///
26 /// Finally, we reserve the highest value, `u32::MAX`, as a sentinel
27 /// that means "infinite". This is separate from the finite costs and
28 /// not reachable by doing arithmetic on them (even when overflowing)
29 /// -- we saturate just *below* infinity. (This is done by the
30 /// `finite()` method.) An infinite cost is used to represent a value
31 /// that cannot be computed, or otherwise serve as a sentinel when
32 /// performing search for the lowest-cost representation of a value.
33 #[derive(Clone, Copy, PartialEq, Eq)]
34 pub(crate) struct Cost(u32);
35 
36 impl core::fmt::Debug for Cost {
37     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38         if *self == Cost::infinity() {
39             write!(f, "Cost::Infinite")
40         } else {
41             f.debug_struct("Cost::Finite")
42                 .field("op_cost", &self.op_cost())
43                 .field("depth", &self.depth())
44                 .finish()
45         }
46     }
47 }
48 
49 impl Ord for Cost {
50     #[inline]
51     fn cmp(&self, other: &Self) -> std::cmp::Ordering {
52         // We make sure that the high bits are the op cost and the low bits are
53         // the depth. This means that we can use normal integer comparison to
54         // order by op cost and then depth.
55         //
56         // We want to break op cost ties with depth (rather than the other way
57         // around). When the op cost is the same, we prefer shallow and wide
58         // expressions to narrow and deep expressions and breaking ties with
59         // `depth` gives us that. For example, `(a + b) + (c + d)` is preferred
60         // to `((a + b) + c) + d`. This is beneficial because it exposes more
61         // instruction-level parallelism and shortens live ranges.
62         self.0.cmp(&other.0)
63     }
64 }
65 
66 impl PartialOrd for Cost {
67     #[inline]
68     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
69         Some(self.cmp(other))
70     }
71 }
72 
73 impl Cost {
74     const DEPTH_BITS: u8 = 8;
75     const DEPTH_MASK: u32 = (1 << Self::DEPTH_BITS) - 1;
76     const OP_COST_MASK: u32 = !Self::DEPTH_MASK;
77     const MAX_OP_COST: u32 = Self::OP_COST_MASK >> Self::DEPTH_BITS;
78 
79     pub(crate) fn infinity() -> Cost {
80         // 2^32 - 1 is, uh, pretty close to infinite... (we use `Cost`
81         // only for heuristics and always saturate so this suffices!)
82         Cost(u32::MAX)
83     }
84 
85     pub(crate) fn zero() -> Cost {
86         Cost(0)
87     }
88 
89     /// Construct a new `Cost` from the given parts.
90     ///
91     /// If the opcode cost is greater than or equal to the maximum representable
92     /// opcode cost, then the resulting `Cost` saturates to infinity.
93     fn new(opcode_cost: u32, depth: u8) -> Cost {
94         if opcode_cost >= Self::MAX_OP_COST {
95             Self::infinity()
96         } else {
97             Cost(opcode_cost << Self::DEPTH_BITS | u32::from(depth))
98         }
99     }
100 
101     fn depth(&self) -> u8 {
102         let depth = self.0 & Self::DEPTH_MASK;
103         u8::try_from(depth).unwrap()
104     }
105 
106     fn op_cost(&self) -> u32 {
107         (self.0 & Self::OP_COST_MASK) >> Self::DEPTH_BITS
108     }
109 
110     /// Return the cost of an opcode.
111     fn of_opcode(op: Opcode) -> Cost {
112         match op {
113             // Constants.
114             Opcode::Iconst | Opcode::F32const | Opcode::F64const => Cost::new(1, 0),
115 
116             // Extends/reduces.
117             Opcode::Uextend
118             | Opcode::Sextend
119             | Opcode::Ireduce
120             | Opcode::Iconcat
121             | Opcode::Isplit => Cost::new(1, 0),
122 
123             // "Simple" arithmetic.
124             Opcode::Iadd
125             | Opcode::Isub
126             | Opcode::Band
127             | Opcode::Bor
128             | Opcode::Bxor
129             | Opcode::Bnot
130             | Opcode::Ishl
131             | Opcode::Ushr
132             | Opcode::Sshr => Cost::new(3, 0),
133 
134             // "Expensive" arithmetic.
135             Opcode::Imul => Cost::new(10, 0),
136 
137             // Everything else.
138             _ => {
139                 // By default, be slightly more expensive than "simple"
140                 // arithmetic.
141                 let mut c = Cost::new(4, 0);
142 
143                 // And then get more expensive as the opcode does more side
144                 // effects.
145                 if op.can_trap() || op.other_side_effects() {
146                     c = c + Cost::new(10, 0);
147                 }
148                 if op.can_load() {
149                     c = c + Cost::new(20, 0);
150                 }
151                 if op.can_store() {
152                     c = c + Cost::new(50, 0);
153                 }
154 
155                 c
156             }
157         }
158     }
159 
160     /// Compute the cost of the operation and its given operands.
161     ///
162     /// Caller is responsible for checking that the opcode came from an instruction
163     /// that satisfies `inst_predicates::is_pure_for_egraph()`.
164     pub(crate) fn of_pure_op(op: Opcode, operand_costs: impl IntoIterator<Item = Self>) -> Self {
165         let c = Self::of_opcode(op) + operand_costs.into_iter().sum();
166         Cost::new(c.op_cost(), c.depth().saturating_add(1))
167     }
168 
169     /// Compute the cost of an operation in the side-effectful skeleton.
170     pub(crate) fn of_skeleton_op(op: Opcode, arity: usize) -> Self {
171         Cost::of_opcode(op) + Cost::new(u32::try_from(arity).unwrap(), (arity != 0) as _)
172     }
173 }
174 
175 impl std::iter::Sum<Cost> for Cost {
176     fn sum<I: Iterator<Item = Cost>>(iter: I) -> Self {
177         iter.fold(Self::zero(), |a, b| a + b)
178     }
179 }
180 
181 impl std::default::Default for Cost {
182     fn default() -> Cost {
183         Cost::zero()
184     }
185 }
186 
187 impl std::ops::Add<Cost> for Cost {
188     type Output = Cost;
189 
190     fn add(self, other: Cost) -> Cost {
191         let op_cost = self.op_cost().saturating_add(other.op_cost());
192         let depth = std::cmp::max(self.depth(), other.depth());
193         Cost::new(op_cost, depth)
194     }
195 }
196 
197 #[cfg(test)]
198 mod tests {
199     use super::*;
200 
201     #[test]
202     fn add_cost() {
203         let a = Cost::new(5, 2);
204         let b = Cost::new(37, 3);
205         assert_eq!(a + b, Cost::new(42, 3));
206         assert_eq!(b + a, Cost::new(42, 3));
207     }
208 
209     #[test]
210     fn add_infinity() {
211         let a = Cost::new(5, 2);
212         let b = Cost::infinity();
213         assert_eq!(a + b, Cost::infinity());
214         assert_eq!(b + a, Cost::infinity());
215     }
216 
217     #[test]
218     fn op_cost_saturates_to_infinity() {
219         let a = Cost::new(Cost::MAX_OP_COST - 10, 2);
220         let b = Cost::new(11, 2);
221         assert_eq!(a + b, Cost::infinity());
222         assert_eq!(b + a, Cost::infinity());
223     }
224 
225     #[test]
226     fn depth_saturates_to_max_depth() {
227         let a = Cost::new(10, u8::MAX);
228         let b = Cost::new(10, 1);
229         assert_eq!(
230             Cost::of_pure_op(Opcode::Iconst, [a, b]),
231             Cost::new(21, u8::MAX)
232         );
233         assert_eq!(
234             Cost::of_pure_op(Opcode::Iconst, [b, a]),
235             Cost::new(21, u8::MAX)
236         );
237     }
238 }
239