cranelift_codegen/egraph/cost.rs
1//! Cost functions for egraph representation.
2
3use 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, Debug, PartialEq, Eq, PartialOrd, Ord)]
34pub(crate) struct Cost(u32);
35impl Cost {
36 pub(crate) fn at_level(&self, loop_level: usize) -> Cost {
37 let loop_level = std::cmp::min(2, loop_level);
38 let multiplier = 1u32 << ((10 * loop_level) as u32);
39 Cost(self.0.saturating_mul(multiplier)).finite()
40 }
41
42 pub(crate) fn infinity() -> Cost {
43 // 2^32 - 1 is, uh, pretty close to infinite... (we use `Cost`
44 // only for heuristics and always saturate so this suffices!)
45 Cost(u32::MAX)
46 }
47
48 pub(crate) fn zero() -> Cost {
49 Cost(0)
50 }
51
52 /// Clamp this cost at a "finite" value. Can be used in
53 /// conjunction with saturating ops to avoid saturating into
54 /// `infinity()`.
55 fn finite(self) -> Cost {
56 Cost(std::cmp::min(u32::MAX - 1, self.0))
57 }
58}
59
60impl std::default::Default for Cost {
61 fn default() -> Cost {
62 Cost::zero()
63 }
64}
65
66impl std::ops::Add<Cost> for Cost {
67 type Output = Cost;
68 fn add(self, other: Cost) -> Cost {
69 Cost(self.0.saturating_add(other.0)).finite()
70 }
71}
72
73/// Return the cost of a *pure* opcode. Caller is responsible for
74/// checking that the opcode came from an instruction that satisfies
75/// `inst_predicates::is_pure_for_egraph()`.
76pub(crate) fn pure_op_cost(op: Opcode) -> Cost {
77 match op {
78 // Constants.
79 Opcode::Iconst | Opcode::F32const | Opcode::F64const => Cost(0),
80 // Extends/reduces.
81 Opcode::Uextend | Opcode::Sextend | Opcode::Ireduce | Opcode::Iconcat | Opcode::Isplit => {
82 Cost(1)
83 }
84 // "Simple" arithmetic.
85 Opcode::Iadd
86 | Opcode::Isub
87 | Opcode::Band
88 | Opcode::Bor
89 | Opcode::Bxor
90 | Opcode::Bnot
91 | Opcode::Ishl
92 | Opcode::Ushr
93 | Opcode::Sshr => Cost(2),
94 // Everything else (pure.)
95 _ => Cost(3),
96 }
97}