Skip to main content

rucc_target/
timing.rs

1//! How long each of a machine's instructions takes, and which part of the machine it takes it on.
2//!
3//! Design: `spec/optimizer/38-scheduling-and-layout.md` sections 38.1 and 38.6.
4//!
5//! A scheduler puts the instructions of a block in the order that finishes soonest, and the only
6//! thing that makes one order finish sooner than another is that the machine does not answer every
7//! instruction in one cycle. So a scheduler needs two numbers about each instruction: how long
8//! after it starts what it wrote may be read, and what it was using while it ran, because two
9//! instructions that want the same part of the machine cannot both start in the same cycle however
10//! independent they are.
11//!
12//! Those two numbers are this. They are a target's answer, for the reason every other description
13//! in this crate is: the pass is in a pipeline crate, `spec/10-backend.md` section 10.8 says a
14//! pipeline crate holds no target specific code, so an opcode is a name to it and how long a name
15//! takes is something it is told.
16//!
17//! # Why the numbers are allowed to be wrong
18//!
19//! They are measurements of a particular processor, taken from published tables, and a program
20//! compiled with them runs on whatever processor the person who runs it has. Spec 10.5 settles
21//! what to do about that: "an incorrect model produces slow code rather than wrong code, which is
22//! the right failure mode". A schedule is a permutation of instructions that were already going to
23//! run, so a model that is wrong about every number produces a program that computes the same
24//! thing at a different speed.
25//!
26//! What a wrong model must not do is be wrong quietly. [`TimingInsts::accurate`] is how a model
27//! says which kind it is, and it is here from the first model rather than added when the first
28//! model turns out to be wrong. `gcc/params.opt:77` has the same flag, `cycle-accurate-model`,
29//! `Init(1)`, and is unusually direct about what it is for: "Whether the scheduling description is
30//! mostly a cycle-accurate model of the target processor and is likely to spill aggressively to
31//! fill any pipeline bubbles."
32//!
33//! A model that says `false` is one whose latencies are worth believing and whose picture of the
34//! machine's units is not, because the latencies come out of a table of measured numbers and the
35//! units are a summary of a pipeline nobody wrote down here. `rucc_codegen::schedule` reads it
36//! exactly that way: it orders by latency either way, and it only holds an instruction back for
37//! want of a free unit when the model says it is worth believing about units.
38//!
39//! # What is not in here
40//!
41//! How many micro-operations an instruction decodes to, which port each of them goes to, and what
42//! the machine does when the queue in front of one fills. That is what a cycle accurate model is
43//! and it is what `gcc/config/*/*.md`'s automata are built out of. No target here has one, every
44//! target here says so, and section 38.8 owes the measurement that says how much that costs.
45
46/// What a scheduler has to know about a machine to put a block in an order.
47#[derive(Debug, Clone, Copy)]
48pub struct TimingInsts {
49    /// What a rule file and the machine IR put in front of this target's opcodes, such as `x64.`.
50    pub prefix: &'static str,
51    /// Which processor the numbers describe, and where they were read out of.
52    ///
53    /// A sentence rather than a name, because the useful thing to know about a model is not what
54    /// it is called but what it was taken from and when. It is printed by `--print-config` and it
55    /// is the first thing anybody comparing two runs of a benchmark wants.
56    pub model: &'static str,
57    /// Whether the numbers are a cycle accurate model of that processor's pipeline.
58    ///
59    /// See the module comment. No target here says `true`, and a target that starts saying it has
60    /// to mean it: the scheduler answers this by enforcing the unit counts below cycle by cycle,
61    /// which turns a wrong unit count from a heuristic that led nowhere into instructions held
62    /// back for a reason that was not real.
63    pub accurate: bool,
64    /// How many instructions the machine starts in one cycle.
65    pub width: u32,
66    /// How many of each unit the machine has.
67    ///
68    /// [`Unit::Free`] and [`Unit::Fixed`] answer with the width, since an instruction that needs no
69    /// unit is held back by nothing but the width, and answering zero would be a machine that
70    /// cannot run a `nop`.
71    pub slots: fn(Unit) -> u32,
72    /// What an instruction of that name costs, or [`None`] for a name this target does not have.
73    ///
74    /// [`None`] rather than a guess, for the reason [`crate::MachineInsts::operands`] answers
75    /// [`None`]: a pass that is told a made up number about an instruction nobody described has no
76    /// way to find out it was made up, and a pass that is told nothing stops.
77    pub timing: fn(&str) -> Option<Timing>,
78}
79
80/// What one instruction costs.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct Timing {
83    /// How many cycles after it starts before what it wrote may be read.
84    ///
85    /// Zero for an instruction that encodes to nothing, which several of this machine's do: they
86    /// are there to tell the allocator where a value already is, and a schedule that thought they
87    /// took a cycle would be a schedule built around instructions that are not in the output.
88    pub latency: u32,
89    /// Which part of the machine it is using while it runs.
90    pub unit: Unit,
91}
92
93/// The parts of a machine a scheduler counts.
94///
95/// A summary of a real processor's ports rather than a description of them. What it has to get
96/// right is which instructions compete with each other, and the ones that compete are the ones
97/// that are scarce: there are several units that add and one that divides, so a block full of
98/// divisions is limited by the divider and a block full of additions is limited by the width.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
100pub enum Unit {
101    /// Ordinary integer work: an addition, a shift, a comparison, a move between registers.
102    Int,
103    /// An integer multiply, which every machine here has fewer of than it has adders.
104    Mul,
105    /// An integer divide, which is the one integer instruction that is not pipelined anywhere.
106    Div,
107    /// A read of memory, including the read folded into an instruction that then does arithmetic.
108    Load,
109    /// A write of memory.
110    Store,
111    /// A branch, a call and a return.
112    Branch,
113    /// Floating point arithmetic, including the conversions between floating point and integers.
114    Float,
115    /// A floating point divide, which is not pipelined for the reason the integer one is not.
116    FloatDiv,
117    /// Nothing the machine has to find room for, which is what an instruction that encodes to
118    /// nothing costs.
119    Free,
120    /// Something the model does not describe, and which nothing may be reordered around.
121    ///
122    /// A fence, a trap, a landing pad, a run of padding a patcher was promised, a hint to a spin
123    /// loop, and the instruction that sets the old floating point unit's rounding mode. Each of
124    /// them is in the function for a reason that is not a value anything reads, so the operands do
125    /// not say what it does and a scheduler reading only the operands would move it or move
126    /// something past it. `rucc_codegen::schedule` stops at one.
127    ///
128    /// It is a unit rather than a flag of its own because a scheduler asks one question of the
129    /// model about each instruction and this is one of the answers: the machine is doing something
130    /// here, and what it is doing is not on the list.
131    Fixed,
132}
133
134impl Unit {
135    /// Every unit, which is what a target's own test walks to check it answered about all of them.
136    pub const ALL: &'static [Self] = &[
137        Self::Int,
138        Self::Mul,
139        Self::Div,
140        Self::Load,
141        Self::Store,
142        Self::Branch,
143        Self::Float,
144        Self::FloatDiv,
145        Self::Free,
146        Self::Fixed,
147    ];
148}
149
150impl PartialEq for TimingInsts {
151    /// Whether the two are the same model, which is what the target's own name for it says.
152    ///
153    /// The two functions are left out. Comparing those would be comparing addresses, and the
154    /// compiler is right that an address says nothing here: one function can have two of them and
155    /// two functions can share one. Every one of these is a `static` a target wrote out by hand
156    /// with its name in [`TimingInsts::model`], so the name is the question anybody holding two of
157    /// these is asking.
158    fn eq(&self, other: &Self) -> bool {
159        self.prefix == other.prefix
160            && self.model == other.model
161            && self.accurate == other.accurate
162            && self.width == other.width
163    }
164}
165
166impl Eq for TimingInsts {}
167
168impl TimingInsts {
169    /// The name with this target's prefix taken off, which is how its own description spells it.
170    #[must_use]
171    pub fn bare<'a>(&self, name: &'a str) -> &'a str {
172        name.strip_prefix(self.prefix).unwrap_or(name)
173    }
174
175    /// What an instruction of that name costs on this machine.
176    #[must_use]
177    pub fn of(&self, name: &str) -> Option<Timing> {
178        (self.timing)(self.bare(name))
179    }
180
181    /// How many of that unit this machine has, never fewer than one.
182    ///
183    /// Never fewer than one because a unit no instruction can ever get a slot on is a scheduler
184    /// that does not terminate, and a target that wrote a zero meant that the unit is not there
185    /// rather than that the instructions needing it never run.
186    #[must_use]
187    pub fn slots(&self, unit: Unit) -> u32 {
188        match unit {
189            Unit::Free | Unit::Fixed => self.width.max(1),
190            unit => (self.slots)(unit).max(1),
191        }
192    }
193}