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 /// Compute the cost of the operation and its given operands. 111 /// 112 /// Caller is responsible for checking that the opcode came from an instruction 113 /// that satisfies `inst_predicates::is_pure_for_egraph()`. 114 pub(crate) fn of_pure_op(op: Opcode, operand_costs: impl IntoIterator<Item = Self>) -> Self { 115 let c = pure_op_cost(op) + operand_costs.into_iter().sum(); 116 Cost::new(c.op_cost(), c.depth().saturating_add(1)) 117 } 118 } 119 120 impl std::iter::Sum<Cost> for Cost { 121 fn sum<I: Iterator<Item = Cost>>(iter: I) -> Self { 122 iter.fold(Self::zero(), |a, b| a + b) 123 } 124 } 125 126 impl std::default::Default for Cost { 127 fn default() -> Cost { 128 Cost::zero() 129 } 130 } 131 132 impl std::ops::Add<Cost> for Cost { 133 type Output = Cost; 134 135 fn add(self, other: Cost) -> Cost { 136 let op_cost = self.op_cost().saturating_add(other.op_cost()); 137 let depth = std::cmp::max(self.depth(), other.depth()); 138 Cost::new(op_cost, depth) 139 } 140 } 141 142 /// Return the cost of a *pure* opcode. 143 /// 144 /// Caller is responsible for checking that the opcode came from an instruction 145 /// that satisfies `inst_predicates::is_pure_for_egraph()`. 146 fn pure_op_cost(op: Opcode) -> Cost { 147 match op { 148 // Constants. 149 Opcode::Iconst | Opcode::F32const | Opcode::F64const => Cost::new(1, 0), 150 151 // Extends/reduces. 152 Opcode::Uextend | Opcode::Sextend | Opcode::Ireduce | Opcode::Iconcat | Opcode::Isplit => { 153 Cost::new(1, 0) 154 } 155 156 // "Simple" arithmetic. 157 Opcode::Iadd 158 | Opcode::Isub 159 | Opcode::Band 160 | Opcode::Bor 161 | Opcode::Bxor 162 | Opcode::Bnot 163 | Opcode::Ishl 164 | Opcode::Ushr 165 | Opcode::Sshr => Cost::new(3, 0), 166 167 // Everything else (pure.) 168 _ => Cost::new(4, 0), 169 } 170 } 171 172 #[cfg(test)] 173 mod tests { 174 use super::*; 175 176 #[test] 177 fn add_cost() { 178 let a = Cost::new(5, 2); 179 let b = Cost::new(37, 3); 180 assert_eq!(a + b, Cost::new(42, 3)); 181 assert_eq!(b + a, Cost::new(42, 3)); 182 } 183 184 #[test] 185 fn add_infinity() { 186 let a = Cost::new(5, 2); 187 let b = Cost::infinity(); 188 assert_eq!(a + b, Cost::infinity()); 189 assert_eq!(b + a, Cost::infinity()); 190 } 191 192 #[test] 193 fn op_cost_saturates_to_infinity() { 194 let a = Cost::new(Cost::MAX_OP_COST - 10, 2); 195 let b = Cost::new(11, 2); 196 assert_eq!(a + b, Cost::infinity()); 197 assert_eq!(b + a, Cost::infinity()); 198 } 199 200 #[test] 201 fn depth_saturates_to_max_depth() { 202 let a = Cost::new(10, u8::MAX); 203 let b = Cost::new(10, 1); 204 assert_eq!( 205 Cost::of_pure_op(Opcode::Iconst, [a, b]), 206 Cost::new(21, u8::MAX) 207 ); 208 assert_eq!( 209 Cost::of_pure_op(Opcode::Iconst, [b, a]), 210 Cost::new(21, u8::MAX) 211 ); 212 } 213 } 214