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}