Skip to main content

rucc_cost/
table.rs

1//! The table a target fills in, and the machinery that makes filling it in completely mandatory.
2//!
3//! Section 40.13 names the failure this module is built around. A target's table is incomplete, a
4//! field is left at zero, and a zero cost makes an operation free, so every heuristic that consults
5//! it goes wrong in the same direction and none of them look broken. The section is explicit about
6//! what a defence has to do: it "must check every field, not merely that the struct was
7//! constructed".
8//!
9//! Rust will not do that on its own. A struct literal that names every field compiles, and so does
10//! one that names half of them and ends in `..Default::default()`, and the second is how a table
11//! ends up with a zero nobody chose. So [`CostTable`] has no `Default`, no public fields to
12//! construct it through, and one way in: a builder that remembers which fields were set and
13//! refuses to hand over a table while any are missing. The completeness check is the constructor,
14//! which means it cannot be the test somebody forgot to write.
15//!
16//! # Two groups, per section 40.3
17//!
18//! GCC's `struct processor_costs` has about 107 fields split in two, and the comment at
19//! `gcc/config/i386/i386.h:114` says why: the register allocator's costs for moving a value between
20//! two places and the expression evaluator's costs for the same operations are different questions
21//! with different answers. The fields below keep that split by name, `move_*` for the allocator and
22//! everything else for the evaluator.
23//!
24//! This table is smaller than GCC's, and the parts that are missing are missing because rucc has no
25//! pass that would read them yet: the vector and mask register costs, the gather and scatter
26//! formulas, the `memcpy` and `memset` strategy tables, the cache and prefetch parameters, and the
27//! alignment strings. Each of those belongs with the pass that needs it, and adding a field nobody
28//! reads is adding a number nobody will check.
29
30use crate::{Bytes, Cycles};
31
32/// An integer width, in the four sizes the machine has instructions for.
33///
34/// Four rather than GCC's five. `mult_init[5]` and `divide[5]` on x86-64 index by mode up to
35/// `TImode`, and rucc lowers 128-bit arithmetic to calls or to pairs of 64-bit operations rather
36/// than costing it as one instruction, so a fifth entry would be a number no pass could use
37/// honestly.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub enum Width {
40    /// One byte.
41    W8,
42    /// Two bytes.
43    W16,
44    /// Four bytes.
45    W32,
46    /// Eight bytes.
47    W64,
48}
49
50impl Width {
51    /// Every width, in order, for a table to be written against.
52    pub const ALL: [Self; 4] = [Self::W8, Self::W16, Self::W32, Self::W64];
53
54    /// Where this width sits in a width-indexed field.
55    #[must_use]
56    pub const fn index(self) -> usize {
57        self as usize
58    }
59
60    /// The width in bits, for a caller that has one and wants the other.
61    #[must_use]
62    pub const fn bits(self) -> u32 {
63        match self {
64            Self::W8 => 8,
65            Self::W16 => 16,
66            Self::W32 => 32,
67            Self::W64 => 64,
68        }
69    }
70
71    /// The width a number of bits names, or nothing for a width the machine has no name for.
72    #[must_use]
73    pub const fn from_bits(bits: u32) -> Option<Self> {
74        match bits {
75            8 => Some(Self::W8),
76            16 => Some(Self::W16),
77            32 => Some(Self::W32),
78            64 => Some(Self::W64),
79            _ => None,
80        }
81    }
82}
83
84/// The shape of a memory address, which is what an addressing mode table is indexed by.
85///
86/// Section 40.9 wants these costed per target and wants the complexity counted per structural
87/// feature, and [`AddrMode::complexity`] is that count. A mode the target does not have is
88/// [`Cycles::INFINITE`] in the table rather than absent from it, so that a pass asking about a mode
89/// gets an answer it can compare instead of an `Option` it has to unwrap into a policy.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
91pub enum AddrMode {
92    /// `[base]`.
93    Base,
94    /// `[base + disp]`.
95    BaseDisp,
96    /// `[base + index]`.
97    BaseIndex,
98    /// `[base + index * scale]`.
99    BaseIndexScale,
100    /// `[base + index * scale + disp]`.
101    BaseIndexScaleDisp,
102}
103
104impl AddrMode {
105    /// Every mode, in order.
106    pub const ALL: [Self; 5] = [
107        Self::Base,
108        Self::BaseDisp,
109        Self::BaseIndex,
110        Self::BaseIndexScale,
111        Self::BaseIndexScaleDisp,
112    ];
113
114    /// Where this mode sits in a mode-indexed field.
115    #[must_use]
116    pub const fn index(self) -> usize {
117        self as usize
118    }
119
120    /// How many structural features this mode has, per section 40.9.
121    ///
122    /// One each for a displacement, an index, and a scale on the index. The base is not counted
123    /// because every mode has one, and a feature every mode has cannot discriminate between them.
124    ///
125    /// This is the absolute count. Section 40.9's refinement, that a feature the target has no
126    /// alternative to should not count against a mode, is applied by [`CostTable::addr_cost`],
127    /// which is where the target is known.
128    #[must_use]
129    pub const fn complexity(self) -> u32 {
130        match self {
131            Self::Base => 0,
132            Self::BaseDisp | Self::BaseIndex => 1,
133            Self::BaseIndexScale => 2,
134            Self::BaseIndexScaleDisp => 3,
135        }
136    }
137
138    /// Whether this mode scales its index.
139    #[must_use]
140    pub const fn scales(self) -> bool {
141        matches!(self, Self::BaseIndexScale | Self::BaseIndexScaleDisp)
142    }
143}
144
145/// Which entries of a field are impossible, for the check that two tables agree about capability.
146///
147/// Section 40.13: "The two tables must differ only in numbers, never in capability, and that is
148/// checkable." A capability is spelled [`Cycles::INFINITE`] in this design, so the check is that
149/// the same entries are infinite in both tables, and that is what this reports. A field that
150/// carries no capability, a count or a size, reports nothing rather than reporting false, so that
151/// a count of 8 for speed and 4 for size is a difference in numbers and passes.
152pub trait Capability {
153    /// One entry per costed lane, saying whether that lane is impossible.
154    fn impossible(&self) -> Vec<bool>;
155}
156
157impl Capability for Cycles {
158    fn impossible(&self) -> Vec<bool> {
159        vec![self.is_infinite()]
160    }
161}
162
163impl<const N: usize> Capability for [Cycles; N] {
164    fn impossible(&self) -> Vec<bool> {
165        self.iter().map(|c| c.is_infinite()).collect()
166    }
167}
168
169impl Capability for u32 {
170    fn impossible(&self) -> Vec<bool> {
171        Vec::new()
172    }
173}
174
175impl Capability for Bytes {
176    fn impossible(&self) -> Vec<bool> {
177        Vec::new()
178    }
179}
180
181/// Declares the cost table, its builder, and the two things that make it checkable.
182///
183/// A macro rather than a hand written struct because the completeness check needs the field list
184/// and a hand written list is one somebody adds a field without updating. The field names exist
185/// three times in the output and once in the source, which is the point.
186macro_rules! cost_table {
187    ($( $(#[$meta:meta])* $name:ident : $ty:ty ),+ $(,)?) => {
188        /// What an operation costs on one target at one optimization goal.
189        ///
190        /// Built through [`Builder`] and no other way, per the module documentation. Every field
191        /// is public to read and none of them can be written after the table exists, because a
192        /// target's costs are data and a pass that adjusts them is a pass keeping a policy
193        /// somewhere nobody can find it.
194        #[derive(Debug, Clone, PartialEq, Eq)]
195        pub struct CostTable {
196            $( $(#[$meta])* pub $name: $ty, )+
197        }
198
199        impl CostTable {
200            /// Every field name, in declaration order.
201            pub const FIELDS: &'static [&'static str] = &[ $( stringify!($name) ),+ ];
202
203            /// Which entries of which fields are impossible, per [`Capability`].
204            #[must_use]
205            pub fn capabilities(&self) -> Vec<(&'static str, Vec<bool>)> {
206                vec![ $( (stringify!($name), Capability::impossible(&self.$name)) ),+ ]
207            }
208        }
209
210        /// The one way to build a [`CostTable`].
211        ///
212        /// Every field starts unset, and [`Builder::build`] refuses to produce a table while any
213        /// of them still is. That is section 40.13's completeness check, moved from a test into
214        /// the constructor so that a target added next year cannot skip it.
215        #[derive(Debug, Clone, Default)]
216        pub struct Builder {
217            $( $name: Option<$ty>, )+
218        }
219
220        impl Builder {
221            /// A table with nothing set yet.
222            #[must_use]
223            pub fn new() -> Self {
224                Self { $( $name: None, )+ }
225            }
226
227            $(
228                $(#[$meta])*
229                // One field is called `add`, so one setter is called `add`, and clippy reads that
230                // as a `std::ops::Add` somebody spelled wrong. The setter is named after the field
231                // and the field is named after the operation, which is the property worth keeping.
232                #[allow(clippy::should_implement_trait)]
233                #[must_use]
234                pub fn $name(mut self, value: $ty) -> Self {
235                    self.$name = Some(value);
236                    self
237                }
238            )+
239
240            /// The fields nobody has set, in declaration order.
241            ///
242            /// Public so that a test can name them, which turns "the table is incomplete" into
243            /// "the table is missing `branch_cost`" without anybody reading a panic message.
244            #[must_use]
245            pub fn missing(&self) -> Vec<&'static str> {
246                let mut missing = Vec::new();
247                $( if self.$name.is_none() { missing.push(stringify!($name)); } )+
248                missing
249            }
250
251            /// The finished table.
252            ///
253            /// # Panics
254            ///
255            /// If any field was left unset, naming them. A target's cost table is written once and
256            /// is a compile time constant of the compiler in every sense that matters, so this
257            /// fires during the tests of whoever added the target and never in front of a user.
258            #[must_use]
259            pub fn build(self) -> CostTable {
260                let missing = self.missing();
261                assert!(
262                    missing.is_empty(),
263                    "the cost table is missing {} of its {} fields: {}. \
264                     A field left unset would be a zero, and a zero cost makes an operation free.",
265                    missing.len(),
266                    CostTable::FIELDS.len(),
267                    missing.join(", "),
268                );
269                CostTable {
270                    $( $name: self.$name.expect("checked just above"), )+
271                }
272            }
273        }
274    };
275}
276
277cost_table! {
278    /// A register to register add, which is the operation [`Cycles::ONE`] is defined as.
279    ///
280    /// It is in the table anyway rather than assumed to be one, because a target where the unit
281    /// operation is not an add should say so instead of having its whole table shifted.
282    add: Cycles,
283
284    /// An address computation that does not touch flags, x86-64's `lea`.
285    ///
286    /// Section 40.3 keeps this separate from `add` because whether it is cheaper is exactly the
287    /// kind of microarchitectural fact that varies between cores of the same target.
288    lea: Cycles,
289
290    /// A shift by an amount known at compile time.
291    shift_const: Cycles,
292
293    /// A shift by an amount in a register, which on x86-64 is the expensive one because of the
294    /// flags dependency and the fixed count register.
295    shift_var: Cycles,
296
297    /// A multiply, indexed by [`Width`].
298    mult: [Cycles; 4],
299
300    /// What each set bit in a constant multiplier adds, for deciding when to expand a multiply by
301    /// a constant into shifts and adds.
302    mult_bit: Cycles,
303
304    /// A divide, indexed by [`Width`]. The most expensive integer operation on every target and
305    /// the reason strength reduction of division is worth doing at all.
306    divide: [Cycles; 4],
307
308    /// A sign extension.
309    movsx: Cycles,
310
311    /// A zero extension, which on x86-64 is free for the 32 to 64 case and is not for the others,
312    /// so this is the cost of the ones that are not free.
313    movzx: Cycles,
314
315    /// A register to register move, as the expression evaluator sees it.
316    reg_move: Cycles,
317
318    /// An integer load, indexed by [`Width`], as the register allocator sees it.
319    move_int_load: [Cycles; 4],
320
321    /// An integer store, indexed by [`Width`], as the register allocator sees it.
322    move_int_store: [Cycles; 4],
323
324    /// A move between two integer registers, as the register allocator sees it.
325    ///
326    /// Separate from `reg_move` on purpose, per section 40.3. The allocator asks what a move it is
327    /// about to insert costs, and the evaluator asks what a move already in the program costs, and
328    /// `gcc/config/i386/i386.h:114` says plainly that the two answers can differ.
329    move_int_reg: Cycles,
330
331    /// A floating point load, for the two widths that exist, single then double.
332    move_fp_load: [Cycles; 2],
333
334    /// A floating point store, single then double.
335    move_fp_store: [Cycles; 2],
336
337    /// A move between two floating point registers.
338    move_fp_reg: Cycles,
339
340    /// A move from a floating point register to an integer one, which goes through memory or a
341    /// dedicated instruction and is never free.
342    move_fp_to_int: Cycles,
343
344    /// A move from an integer register to a floating point one.
345    move_int_to_fp: Cycles,
346
347    /// An address of each shape, indexed by [`AddrMode`], per section 40.9.
348    ///
349    /// A mode the target does not have is [`Cycles::INFINITE`], which is what the check that the
350    /// speed and size tables agree about capability reads.
351    addr: [Cycles; 5],
352
353    /// What an unpredictable branch costs when optimizing for speed, per section 40.5.
354    ///
355    /// Only the unpredictable case is a target number. `BRANCH_COST` at
356    /// `gcc/config/i386/i386.h:2023` makes a predictable branch free and a branch costed for size
357    /// worth 2 on every target, and those two are in [`crate::heuristics`] rather than here
358    /// because they are not facts about the machine.
359    branch_cost: Cycles,
360
361    /// What a mispredicted branch costs, per section 40.10.
362    ///
363    /// The number that decides whether a switch becomes a jump table, because an indirect branch
364    /// with many targets has to be priced as a mispredict and not as a branch.
365    mispredict_penalty: Cycles,
366
367    /// How many scalar moves a block copy may expand to before it becomes a call, per section 40.7.
368    ///
369    /// GCC's `move_ratio`. A count of moves rather than of bytes, because how many moves a copy
370    /// takes depends on the alignment the compiler can prove.
371    move_ratio: u32,
372
373    /// The same for a block fill. GCC's `clear_ratio`.
374    clear_ratio: u32,
375
376    /// The narrowest store worth using, per section 40.7's trimming rule.
377    ///
378    /// A partially dead store is trimmed only to a width at least this wide. Narrowing an 8-byte
379    /// store to a 1-byte store because seven bytes are dead is legal and is usually a store
380    /// forwarding stall, which is the thing this number stops.
381    cheapest_store: Bytes,
382
383    /// How many integer operations the machine issues in parallel, per section 40.8.
384    ///
385    /// The reassociation width. A chain of eight adds becomes a tree only on a machine that can
386    /// execute the tree's independent operations at once, so this is a hardware fact rather than a
387    /// tuning constant, and it defaults to 1 on a new target, meaning no reassociation.
388    reassoc_int: u32,
389
390    /// The same for floating point.
391    ///
392    /// Reassociating floating point needs `-ffast-math` whatever this says, because the
393    /// transformation is not value preserving. This is only how wide the tree may be once that
394    /// question has been answered somewhere else.
395    reassoc_fp: u32,
396}
397
398impl CostTable {
399    /// A fresh builder, which is the only way to a table.
400    #[must_use]
401    pub fn builder() -> Builder {
402        Builder::new()
403    }
404
405    /// What a multiply of this width costs.
406    #[must_use]
407    pub fn mult_of(&self, width: Width) -> Cycles {
408        self.mult[width.index()]
409    }
410
411    /// What a divide of this width costs.
412    #[must_use]
413    pub fn divide_of(&self, width: Width) -> Cycles {
414        self.divide[width.index()]
415    }
416
417    /// What an integer load of this width costs the allocator.
418    #[must_use]
419    pub fn int_load(&self, width: Width) -> Cycles {
420        self.move_int_load[width.index()]
421    }
422
423    /// What an integer store of this width costs the allocator.
424    #[must_use]
425    pub fn int_store(&self, width: Width) -> Cycles {
426        self.move_int_store[width.index()]
427    }
428
429    /// Whether the target has this addressing mode at all.
430    #[must_use]
431    pub fn has_addr(&self, mode: AddrMode) -> bool {
432        !self.addr[mode.index()].is_infinite()
433    }
434
435    /// What an address of this shape costs, with the complexity counted relative to the target.
436    ///
437    /// Section 40.9's refinement, and the comment it comes from at
438    /// `gcc/tree-ssa-loop-ivopts.cc:4799`: "Don't increase the complexity of adding a scaled index
439    /// if it's the only kind of index that the target allows". A feature the target offers no
440    /// alternative to is not a complication, and counting it as one makes every address on that
441    /// target look complicated, which is the same as the tiebreak not working.
442    #[must_use]
443    pub fn addr_cost(&self, mode: AddrMode) -> crate::Cost {
444        let cycles = self.addr[mode.index()];
445        if cycles.is_infinite() {
446            return crate::Cost::INFINITE;
447        }
448        let mut complexity = mode.complexity();
449        // The scale is free to write down if there is no unscaled index mode to write instead.
450        if mode.scales() && !self.has_addr(AddrMode::BaseIndex) {
451            complexity -= 1;
452        }
453        crate::Cost::new(cycles, complexity)
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use super::{AddrMode, Builder, CostTable, Width};
460    use crate::{Bytes, Cost, Cycles};
461
462    /// A table with every field set to something, for tests about the mechanism rather than the
463    /// numbers. The numbers a real target uses are tested in that target's own module.
464    fn filled() -> Builder {
465        let one = Cycles::ONE;
466        CostTable::builder()
467            .add(one)
468            .lea(one)
469            .shift_const(one)
470            .shift_var(one)
471            .mult([one; 4])
472            .mult_bit(one)
473            .divide([one; 4])
474            .movsx(one)
475            .movzx(one)
476            .reg_move(one)
477            .move_int_load([one; 4])
478            .move_int_store([one; 4])
479            .move_int_reg(one)
480            .move_fp_load([one; 2])
481            .move_fp_store([one; 2])
482            .move_fp_reg(one)
483            .move_fp_to_int(one)
484            .move_int_to_fp(one)
485            .addr([one; 5])
486            .branch_cost(one)
487            .mispredict_penalty(one)
488            .move_ratio(8)
489            .clear_ratio(8)
490            .cheapest_store(Bytes(4))
491            .reassoc_int(1)
492            .reassoc_fp(1)
493    }
494
495    #[test]
496    fn a_full_table_builds() {
497        let table = filled().build();
498        assert_eq!(table.add, Cycles::ONE);
499        assert!(!CostTable::FIELDS.is_empty());
500    }
501
502    #[test]
503    fn an_empty_builder_is_missing_every_field() {
504        assert_eq!(Builder::new().missing(), CostTable::FIELDS);
505    }
506
507    #[test]
508    #[should_panic(expected = "branch_cost")]
509    fn a_table_missing_a_field_does_not_build_and_says_which() {
510        // The failure section 40.13 is about. It has to be impossible to reach a table with a
511        // field nobody set, because that field would read as zero and a zero cost is free.
512        let mut incomplete = filled();
513        incomplete.branch_cost = None;
514        let _ = incomplete.build();
515    }
516
517    #[test]
518    fn setting_a_field_twice_keeps_the_second() {
519        let table = filled().add(Cycles::insns(7)).build();
520        assert_eq!(table.add, Cycles::insns(7));
521    }
522
523    #[test]
524    fn every_field_name_is_distinct() {
525        // The macro would happily declare two fields with the same name and the compiler would
526        // stop it, but the name list is what the completeness message prints, so it is worth
527        // knowing that two entries in it never mean the same field.
528        let mut names = CostTable::FIELDS.to_vec();
529        names.sort_unstable();
530        let before = names.len();
531        names.dedup();
532        assert_eq!(names.len(), before);
533    }
534
535    #[test]
536    fn widths_index_their_own_slots() {
537        for (slot, width) in Width::ALL.iter().enumerate() {
538            assert_eq!(width.index(), slot);
539            assert_eq!(Width::from_bits(width.bits()), Some(*width));
540        }
541        assert_eq!(Width::from_bits(128), None);
542        assert_eq!(Width::from_bits(1), None);
543    }
544
545    #[test]
546    fn an_address_gets_one_point_of_complexity_per_feature() {
547        assert_eq!(AddrMode::Base.complexity(), 0);
548        assert_eq!(AddrMode::BaseDisp.complexity(), 1);
549        assert_eq!(AddrMode::BaseIndex.complexity(), 1);
550        assert_eq!(AddrMode::BaseIndexScale.complexity(), 2);
551        assert_eq!(AddrMode::BaseIndexScaleDisp.complexity(), 3);
552    }
553
554    #[test]
555    fn a_mode_the_target_lacks_is_impossible_rather_than_expensive() {
556        let mut addrs = [Cycles::ONE; 5];
557        addrs[AddrMode::BaseIndexScaleDisp.index()] = Cycles::INFINITE;
558        let table = filled().addr(addrs).build();
559        assert!(!table.has_addr(AddrMode::BaseIndexScaleDisp));
560        assert_eq!(table.addr_cost(AddrMode::BaseIndexScaleDisp), Cost::INFINITE);
561        assert!(table.has_addr(AddrMode::Base));
562    }
563
564    #[test]
565    fn a_scale_the_target_has_no_alternative_to_does_not_count_as_a_complication() {
566        // Section 40.9's refinement. On a machine whose only index mode is scaled, an address
567        // with a scaled index is the plain one, and charging it for the scale would make every
568        // address on that target tie at the same complexity.
569        let mut addrs = [Cycles::ONE; 5];
570        addrs[AddrMode::BaseIndex.index()] = Cycles::INFINITE;
571        let scaled_only = filled().addr(addrs).build();
572        assert_eq!(scaled_only.addr_cost(AddrMode::BaseIndexScale).complexity, 1);
573
574        let both = filled().build();
575        assert_eq!(both.addr_cost(AddrMode::BaseIndexScale).complexity, 2);
576    }
577}