Path: blob/main/cranelift/codegen/src/egraph/cost.rs
3068 views
//! Cost functions for egraph representation.12use crate::ir::Opcode;34/// A cost of computing some value in the program.5///6/// Costs are measured in an arbitrary union that we represent in a7/// `u32`. The ordering is meant to be meaningful, but the value of a8/// single unit is arbitrary (and "not to scale"). We use a collection9/// of heuristics to try to make this approximation at least usable.10///11/// We start by defining costs for each opcode (see `pure_op_cost`12/// below). The cost of computing some value, initially, is the cost13/// of its opcode, plus the cost of computing its inputs.14///15/// We then adjust the cost according to loop nests: for each16/// loop-nest level, we multiply by 1024. Because we only have 3217/// bits, we limit this scaling to a loop-level of two (i.e., multiply18/// by 2^20 ~= 1M).19///20/// Arithmetic on costs is always saturating: we don't want to wrap21/// around and return to a tiny cost when adding the costs of two very22/// expensive operations. It is better to approximate and lose some23/// precision than to lose the ordering by wrapping.24///25/// Finally, we reserve the highest value, `u32::MAX`, as a sentinel26/// that means "infinite". This is separate from the finite costs and27/// not reachable by doing arithmetic on them (even when overflowing)28/// -- we saturate just *below* infinity. (This is done by the29/// `finite()` method.) An infinite cost is used to represent a value30/// that cannot be computed, or otherwise serve as a sentinel when31/// performing search for the lowest-cost representation of a value.32#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]33pub(crate) struct Cost(u32);3435impl core::fmt::Debug for Cost {36fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {37if *self == Cost::infinity() {38write!(f, "Cost::Infinite")39} else {40f.debug_tuple("Cost::Finite").field(&self.cost()).finish()41}42}43}4445impl Cost {46pub(crate) fn infinity() -> Cost {47// 2^32 - 1 is, uh, pretty close to infinite... (we use `Cost`48// only for heuristics and always saturate so this suffices!)49Cost(u32::MAX)50}5152pub(crate) fn zero() -> Cost {53Cost(0)54}5556/// Construct a new `Cost`.57fn new(cost: u32) -> Cost {58Cost(cost)59}6061fn cost(&self) -> u32 {62self.063}6465/// Return the cost of an opcode.66fn of_opcode(op: Opcode) -> Cost {67match op {68// Constants.69Opcode::Iconst | Opcode::F32const | Opcode::F64const => Cost::new(1),7071// Extends/reduces.72Opcode::Uextend73| Opcode::Sextend74| Opcode::Ireduce75| Opcode::Iconcat76| Opcode::Isplit => Cost::new(1),7778// "Simple" arithmetic.79Opcode::Iadd80| Opcode::Isub81| Opcode::Band82| Opcode::Bor83| Opcode::Bxor84| Opcode::Bnot85| Opcode::Ishl86| Opcode::Ushr87| Opcode::Sshr => Cost::new(3),8889// "Expensive" arithmetic.90Opcode::Imul => Cost::new(10),9192// Everything else.93_ => {94// By default, be slightly more expensive than "simple"95// arithmetic.96let mut c = Cost::new(4);9798// And then get more expensive as the opcode does more side99// effects.100if op.can_trap() || op.other_side_effects() {101c = c + Cost::new(10);102}103if op.can_load() {104c = c + Cost::new(20);105}106if op.can_store() {107c = c + Cost::new(50);108}109110c111}112}113}114115/// Compute the cost of the operation and its given operands.116///117/// Caller is responsible for checking that the opcode came from an instruction118/// that satisfies `inst_predicates::is_pure_for_egraph()`.119pub(crate) fn of_pure_op(op: Opcode, operand_costs: impl IntoIterator<Item = Self>) -> Self {120let c = Self::of_opcode(op) + operand_costs.into_iter().sum();121Cost::new(c.cost())122}123124/// Compute the cost of an operation in the side-effectful skeleton.125pub(crate) fn of_skeleton_op(op: Opcode, arity: usize) -> Self {126Cost::of_opcode(op) + Cost::new(u32::try_from(arity).unwrap())127}128}129130impl core::iter::Sum<Cost> for Cost {131fn sum<I: Iterator<Item = Cost>>(iter: I) -> Self {132iter.fold(Self::zero(), |a, b| a + b)133}134}135136impl core::default::Default for Cost {137fn default() -> Cost {138Cost::zero()139}140}141142impl core::ops::Add<Cost> for Cost {143type Output = Cost;144145fn add(self, other: Cost) -> Cost {146Cost::new(self.cost().saturating_add(other.cost()))147}148}149150#[cfg(test)]151mod tests {152use super::*;153154#[test]155fn add_cost() {156let a = Cost::new(5);157let b = Cost::new(37);158assert_eq!(a + b, Cost::new(42));159assert_eq!(b + a, Cost::new(42));160}161162#[test]163fn add_infinity() {164let a = Cost::new(5);165let b = Cost::infinity();166assert_eq!(a + b, Cost::infinity());167assert_eq!(b + a, Cost::infinity());168}169170#[test]171fn op_cost_saturates_to_infinity() {172let a = Cost::new(u32::MAX - 10);173let b = Cost::new(11);174assert_eq!(a + b, Cost::infinity());175assert_eq!(b + a, Cost::infinity());176}177}178179180