rucc_cost/lib.rs
1//! What things cost on a target, and every tuning constant the optimizer has.
2//!
3//! This is document 40 of `spec/optimizer/`, made real. The crate exists because of the failure
4//! that document opens with: a compiler where each pass has its own idea of what an operation
5//! costs is a compiler where two passes undo each other, and neither of them is wrong. Putting the
6//! numbers in one place does not make them right, but it makes them one thing that can be measured
7//! and changed, instead of thirty things that have to be found first.
8//!
9//! # What is in here
10//!
11//! [`Cycles`] and [`Bytes`], which are separate types so that a cost in time is never compared
12//! against a threshold in space. [`Cost`], which is a time and a complexity compared
13//! lexicographically with an explicit infinity, from GCC's `comp_cost`. [`CostTable`], which a
14//! target fills in completely or not at all. [`TuneFlag`], which is the half of a cost model that
15//! is a boolean rather than a number. And [`heuristics`], which is the file every threshold in
16//! every pass has to come from.
17//!
18//! # Two tables, not one table and a policy
19//!
20//! Section 40.3 reads `ix86_cur_cost()` at `gcc/config/i386/i386.h:269` and takes the design
21//! from it: optimizing for size is a different cost table, not a weighting applied to the same
22//! one. `-Os` selects the second table and every pass then goes on asking the same questions in
23//! the same way. It makes `-Os` behaviour inspectable as data, and it means no pass has to
24//! remember to ask whether it is optimizing for size, which is the sort of thing a pass forgets
25//! in exactly one of its five decisions.
26//!
27//! # What is not in here
28//!
29//! Anything derived from a function. Register pressure, block frequency and branch predictability
30//! are all things section 40.6 wants computed once per function and shared, and all three need the
31//! IR, so they belong with the analyses rather than with the target description. This crate is
32//! below the IR on purpose.
33
34#![doc(html_root_url = "https://docs.rs/rucc-cost/0.10.2")]
35
36pub mod cost;
37pub mod cycles;
38pub mod heuristics;
39pub mod table;
40pub mod tune;
41pub mod x86_64;
42
43pub use cost::{Complexity, Cost};
44pub use cycles::{Bytes, Cycles};
45pub use table::{AddrMode, Builder, CostTable, Width};
46pub use tune::{TuneFlag, Tuning};
47
48use rucc_target::Arch;
49use rucc_tuple::TargetTuple;
50
51/// Which of a target's two tables is wanted.
52///
53/// A named type rather than a bare `bool`, because `table(true)` at a call site is a coin toss for
54/// the reader and `table(Goal::Size)` is not.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub enum Goal {
57 /// Make it fast. `-O1`, `-O2`, `-O3`.
58 Speed,
59 /// Make it small. `-Os` and `-Oz`.
60 Size,
61}
62
63impl Goal {
64 /// The goal for a level that has already been asked whether it optimizes for size.
65 ///
66 /// Takes the answer rather than the level, because the level lives in `rucc-session` and this
67 /// crate sits below it. That is not a workaround for the layer rule, it is the layer rule
68 /// working: what an instruction costs on a machine has nothing to do with how the driver was
69 /// invoked, and a dependency the other way would say it did. The caller writes
70 /// `Goal::for_size(level.is_size())`, which is one line and reads correctly.
71 #[must_use]
72 pub const fn for_size(size: bool) -> Self {
73 if size { Self::Size } else { Self::Speed }
74 }
75
76 /// The goal as it appears in a dump.
77 #[must_use]
78 pub const fn as_str(self) -> &'static str {
79 match self {
80 Self::Speed => "speed",
81 Self::Size => "size",
82 }
83 }
84}
85
86impl std::fmt::Display for Goal {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.write_str(self.as_str())
89 }
90}
91
92/// Which bank of registers a value needs.
93///
94/// Two, which is the split every target rucc has a back end for or plans one for. A vector lands
95/// in the floating point bank because on x86-64 the same registers hold both, and a target where
96/// that is wrong is a target that needs a third variant here rather than a different rule.
97///
98/// It is in this crate rather than with the pressure analysis that counts them because the target
99/// is what says how many of each it hands out, and this crate is where a target is described. The
100/// question of which bank holds a given IR type is the other half and it stays with the analysis,
101/// since this crate sits below the IR.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub enum RegClass {
104 /// Integers, pointers and capabilities, which the general purpose registers hold.
105 Integer,
106 /// Floating point and vectors, which on every target rucc targets share a bank.
107 Float,
108}
109
110impl RegClass {
111 /// Both of them, for a caller that reports each.
112 pub const ALL: [Self; 2] = [Self::Integer, Self::Float];
113
114 /// How many there are, for the arrays keyed by one.
115 pub const COUNT: usize = Self::ALL.len();
116
117 /// Where this bank sits in a class-indexed array.
118 #[must_use]
119 pub const fn index(self) -> usize {
120 self as usize
121 }
122
123 /// How it reads in a dump.
124 #[must_use]
125 pub const fn as_str(self) -> &'static str {
126 match self {
127 Self::Integer => "integer",
128 Self::Float => "float",
129 }
130 }
131}
132
133impl std::fmt::Display for RegClass {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.write_str(self.as_str())
136 }
137}
138
139/// What a pass asks a target about costs, per section 40.12.
140///
141/// Three kinds of answer: a number that comes from one of the two tables, a boolean that does not
142/// depend on the goal at all, and a number that does not either. Whether a microarchitecture
143/// prefers an `lea` to an `add` is not a different fact when optimizing for size, and neither is
144/// how many registers the allocator hands out.
145pub trait TargetCosts: Send + Sync {
146 /// The table for this goal.
147 fn table(&self, goal: Goal) -> &CostTable;
148
149 /// What this target answers for a tuning flag.
150 fn tune(&self, flag: TuneFlag) -> bool;
151
152 /// How many registers of that bank the allocator will actually hand out, per section 40.6.
153 ///
154 /// Not how many the machine has. The number a pass wants is how many a value may be put in
155 /// after the ones with a job of their own are taken out, because a pass that hoists up to the
156 /// architectural count has hoisted into registers that were never available and the spill it
157 /// caused lands inside the loop it was trying to help.
158 ///
159 /// There is no default. A target that has not answered this has not been asked how its
160 /// register file is spent, and a default here would be a number nobody chose that every pass
161 /// consulting it would believe.
162 fn allocatable(&self, class: RegClass) -> u32;
163
164 /// What the target is called in a dump.
165 fn name(&self) -> &'static str;
166
167 /// What an unpredictable branch costs at this goal, per section 40.5.
168 ///
169 /// Provided rather than left to each pass, because `BRANCH_COST` at
170 /// `gcc/config/i386/i386.h:2023` is three cases in one line and getting one of them wrong is
171 /// how a well predicted branch ends up if-converted.
172 ///
173 /// Two of the three cases read the table. Optimizing for speed, a predictable branch is free
174 /// and an unpredictable one costs whatever the target says; optimizing for size, a branch is
175 /// the same number of bytes either way, because the branch predictor does not shorten the
176 /// encoding. The one case that does not read the table is the free one, and it does not
177 /// because zero is a claim about hardware rather than about this machine.
178 fn branch_cost(&self, goal: Goal, predictable: bool) -> Cycles {
179 if goal == Goal::Speed && predictable {
180 return heuristics::BRANCH_COST_PREDICTABLE;
181 }
182 self.table(goal).branch_cost
183 }
184}
185
186/// The costs for a target, or nothing for one nobody has written a table for.
187///
188/// x86-64 is the only answer today, because it is the only back end rucc has. The function exists
189/// anyway so that the second target is a file and a match arm rather than a redesign.
190#[must_use]
191pub fn for_arch(arch: Arch) -> Option<&'static dyn TargetCosts> {
192 match arch {
193 Arch::X86_64 => Some(x86_64::COSTS),
194 _ => None,
195 }
196}
197
198/// The costs for the target a tuple names, which is what a caller holding a module asks.
199///
200/// Two enumerations name the same three machines. `rucc_tuple::Arch` is what a target tuple
201/// carries and it can spell architectures rucc has no back end for, and [`Arch`] is what the cost
202/// tables are keyed by because it is the list of architectures a back end exists for. So the
203/// mapping is partial, and everything it does not name answers the same as an architecture with a
204/// name and no table.
205///
206/// It is here rather than at the caller because there is one right mapping and a second copy of it
207/// somewhere else would be a place for the two to disagree.
208#[must_use]
209pub fn for_tuple(tuple: TargetTuple) -> Option<&'static dyn TargetCosts> {
210 let arch = match tuple.arch() {
211 rucc_tuple::Arch::X86_64 => Arch::X86_64,
212 rucc_tuple::Arch::Aarch64 => Arch::Aarch64,
213 rucc_tuple::Arch::Riscv64 => Arch::Riscv64,
214 _ => return None,
215 };
216 for_arch(arch)
217}
218
219#[cfg(test)]
220mod tests {
221 use super::{Goal, TuneFlag, for_arch};
222 use rucc_target::Arch;
223
224 #[test]
225 fn the_only_backend_has_costs() {
226 let costs = for_arch(Arch::X86_64).expect("x86-64 is the back end rucc has");
227 assert_eq!(costs.name(), "x86-64");
228 assert!(!costs.table(Goal::Speed).add.is_infinite());
229 }
230
231 #[test]
232 fn a_target_with_no_back_end_has_no_costs_rather_than_made_up_ones() {
233 // The alternative would be a default table, and a default table is a set of numbers
234 // nobody chose that every pass would believe.
235 assert!(for_arch(Arch::Aarch64).is_none());
236 }
237
238 #[test]
239 fn a_tuning_flag_does_not_depend_on_the_goal() {
240 // There is nothing to assert against here except the shape of the interface: `tune` takes
241 // no goal, so it cannot answer differently for `-Os`. The test is here to fail if
242 // somebody adds one.
243 let costs = for_arch(Arch::X86_64).unwrap();
244 for flag in TuneFlag::ALL {
245 let _: bool = costs.tune(flag);
246 }
247 }
248}