rucc_codegen/schedule.rs
1//! Putting the instructions of a block in the order that finishes soonest.
2//!
3//! Design: `spec/optimizer/38-scheduling-and-layout.md` sections 38.1, 38.6 and 38.7.
4//!
5//! Every instruction in a block is going to run, in some order, and the orders that compute the
6//! same thing are the ones that keep each instruction behind the ones it reads from. Among those
7//! orders, one finishes before the others, because the machine does not answer every instruction in
8//! one cycle: a multiply takes three, a load takes five, and an instruction that reads what one of
9//! them wrote cannot start until it is done. Putting independent work in those cycles rather than
10//! waiting is the whole of this pass.
11//!
12//! # The algorithm, and where it comes from
13//!
14//! A list scheduler, which is `gcc/haifa-sched.cc`'s. Build a graph of what depends on what, take
15//! the instructions whose dependences are all satisfied, choose one, repeat. Everything a scheduler
16//! is is in how it chooses, and `gcc/haifa-sched.cc:55` writes that out as a list of eight
17//! tiebreaks. Section 38.1 goes through them and says which are rucc's: one, two, six, seven and
18//! eight. Three, four and five are about moving instructions between blocks and about moving them
19//! where they might not have run, and this pass does neither.
20//!
21//! So what this pass chooses by is five numbers in that order:
22//!
23//! 1. The longest path from here to the end of the run, in cycles. This is the criterion, and the
24//! other four are for when it ties. An instruction on the critical path delays everything behind
25//! it by exactly as much as it is delayed, and one that is not on it is free until it is.
26//! 2. How many more registers are live after it than before. Section 38.1 quotes
27//! `gcc/haifa-sched.cc:87` on what this is for: "if an operation requires that constants be
28//! loaded into registers, it is certainly desirable to load those constants as early as
29//! necessary, but no earlier". An instruction that writes a register and reads nothing that dies
30//! is one whose value now has to be kept somewhere, and hoisting it to the top of a block
31//! because it depends on nothing is the classic way a scheduler makes a function worse.
32//! 3. Whether it reads what the instruction just scheduled wrote. It does not have to, since the
33//! graph would have stopped it if it were not allowed, but one that does will wait and one that
34//! does not will not.
35//! 4. How many instructions depend on it. Scheduling one of these makes more work available to
36//! choose from later, which is what keeps the ready list from running dry.
37//! 5. Where it was to start with. This is not a heuristic. It is what makes the output a function
38//! of the input, and it has to be a position rather than anything that comes out of a hash map,
39//! for the reason `spec/10-backend.md` gives about a compiler whose output moves between runs.
40//!
41//! # Why it runs after the registers are handed out
42//!
43//! Section 38.6 decides it: "One scheduler, after allocation, before the layout freeze." The
44//! argument section 38.7 makes for that placement is the one that matters here. The dominant way a
45//! scheduler makes a program worse is by holding more values live at once than there are registers,
46//! so the allocator spills, and the spill costs more than the latency the schedule hid. After
47//! allocation that cannot happen: every value is already in a register, no reordering this pass can
48//! make changes which register anything is in, and nothing is left that could decide to spill.
49//!
50//! What it costs is that the registers are the constraint instead. Before allocation a value is
51//! written once, so the only dependence between two instructions is that one reads what the other
52//! wrote. Afterwards the same register holds a dozen different values over a block, so an
53//! instruction that writes one has to stay behind everything that reads what was in it, and those
54//! orderings are real even though no value passes between the two instructions. That is most of
55//! what the graph below is made of, and it is why this pass finds less to do than one before
56//! allocation would.
57//!
58//! How much less has now been measured, and the honest answer is almost all of it. Five programs
59//! built with this on and with it off, best of five runs each, on a six core Xeon with gcc 16 as
60//! the reference:
61//!
62//! ```text
63//! program off on accurate gcc-16 -O2
64//! ilp 75 75 78 60
65//! serial 213 212 215 58
66//! mem 47 49 49 40
67//! fp 271 267 266 108
68//! branchy 119 122 121 81
69//! ```
70//!
71//! Milliseconds, and the run to run spread on this machine is a few of them, so every column here
72//! is the same column. That is the measurement section 38.8 asked for and it says this pass is
73//! currently worth nothing on these five programs. Two reasons, and the first is the one above: by
74//! the time this runs the registers have been handed out, so the same register holds a dozen values
75//! over a block and the anti and output edges that creates pin most of the order in place. The
76//! second is that the gap to gcc is not a scheduling gap. A factor of three and a half on `serial`
77//! and two and a half on `fp` is work gcc did before it got anywhere near an instruction order, and
78//! no permutation of the instructions rucc emits closes it.
79//!
80//! The pass stays, at `-O2` and above, for what it costs rather than for what it currently returns:
81//! it is sound, it is cheap, and it is the thing that has to exist before the latencies in
82//! [`rucc_target::TimingInsts`] mean anything at all. The column worth watching is `accurate`, which
83//! is the same model told to believe its own unit counts, and which is slightly worse on the one
84//! program with real instruction level parallelism in it. That is the model being wrong about units
85//! in exactly the way [`rucc_target::TimingInsts::accurate`] says it is, and it is why x86-64
86//! answers `false`.
87//!
88//! # What the graph is made of
89//!
90//! Four kinds of edge, and the first three are `gcc/sched-deps.cc`'s `REG_DEP_TRUE`,
91//! `REG_DEP_OUTPUT` and `REG_DEP_ANTI` over registers:
92//!
93//! - One instruction reads a register another wrote, so it waits for the value.
94//! - Two instructions write the same register, so they stay in order or the register ends up
95//! holding the wrong one of them.
96//! - One instruction writes a register another read, so the read stays in front of the write.
97//!
98//! The fourth is the condition state, which on this kind of machine is a register nobody named. It
99//! is not in an operand vector, so the three kinds above do not see it, and the target says which
100//! instructions write it and which read it. Getting this wrong is a miscompile and the failure
101//! looks like a target description that forgot a clobber, which section 38.7 says is the same root
102//! cause as every other missing-clobber bug.
103//!
104//! # Memory, and why it is one chain
105//!
106//! Every instruction that touches memory or computes an address stays in the order it was in,
107//! relative to every other one. That is stronger than it has to be. `gcc/haifa-sched.cc:71` is
108//! candid about the trade: "only if we can be certain that memory references are not part of the
109//! data dependency graph... can we move operations past memory references. To first approximation,
110//! reads can be done independently, while writes introduce dependencies."
111//!
112//! rucc cannot take the first approximation here. Machine IR does not carry `volatile`, which
113//! [`crate::copies`] says at length: a read the program insisted on and an ordinary one are the
114//! same instruction with the same operands by the time this runs. So two reads are not
115//! interchangeable either, and the only safe answer at this level is to leave the accesses in the
116//! order they arrived in. That is also the answer [`crate::combine`] gives, for the same reason and
117//! through the same question to the target.
118//!
119//! Address computation is in the chain as well, and not because an address is a memory access. It
120//! is because the stack pointer moves without saying so. A push and a pop change it and name it in
121//! no operand, so an address counted from it means different things on either side of one, and
122//! anything that carries an addressing mode is something that could be counted from it. Putting
123//! them all in one chain costs a little freedom around `lea` and needs no new question of the
124//! target.
125//!
126//! # What nothing moves across
127//!
128//! A call, because what a call does to memory and to the registers a convention does not preserve
129//! is not in its operands. A branch or a return, for the same reason: a `ret` reads the value in
130//! `rax` without naming it. One only turns up in the middle of a block when an `asm` template put
131//! it there, and a naked function's `movl $42, %eax; ret` is the case that found it, where the
132//! `ret` was moved above the `mov`. An instruction the target does not describe, on the same reasoning
133//! backwards. An instruction the target describes as doing something the timing model does not
134//! cover, which is [`Unit::Fixed`]: a fence, a trap, a landing pad, the padding a patcher was
135//! promised. And an instruction that carries a frame rule, because those rules say what the
136//! unwinder should believe at each address in the prologue and the epilogue, and an instruction
137//! that moves takes its rule with it to an address where it is not true.
138//!
139//! The last instruction of a block, as well, along with whatever the caller has pinned. What a
140//! block leaves on is the last thing in it by the time [`crate::layout`] runs, and the layout is
141//! what turns the arms of a block into jumps, so a block whose condition is not at the end of it is
142//! a block the layout cannot write. What the caller pins is the comparison the layout is going to
143//! fuse with that condition, since the two have to stay next to each other for the fusion to
144//! happen and nothing here would otherwise keep them there.
145//!
146//! Each of those splits the block into runs, and a run is scheduled on its own with everything
147//! before and after it left where it was. A block with no barrier in it is one run.
148//!
149//! # The bound
150//!
151//! [`READY`] instructions are considered at each step and no more, which is
152//! `gcc/params.opt:761`'s `max-sched-ready-insns`, `Init(100)`, and section 38.8 asks for the same
153//! bound for the same reason: choosing is linear in the ready list and the ready list can be as
154//! long as the block. [`LONGEST`] is the second half of it, a run this pass will not build a graph
155//! for at all, because building one is quadratic in the worst case and a block of several thousand
156//! machine instructions is a generated table rather than something anybody is waiting on.
157//!
158//! # What makes it correct
159//!
160//! The order this writes is a topological order of the graph, and nothing else about the pass is
161//! load bearing. The timing model chooses among the orders the graph allows and cannot choose one
162//! it does not allow, so a model that is wrong about every number produces a slower program and not
163//! a different one, which is what spec 10.5 says the right failure mode is. What has to be right is
164//! the graph, and what makes the graph right is that every edge the machine needs is in it.
165
166use std::collections::{BTreeSet, HashMap, HashSet};
167
168use rucc_base::{Interner, Symbol};
169use rucc_mir::{Block, Func, Inst, Reg, Role};
170use rucc_target::{FlagInsts, MachineInsts, RegClass, Timing, TimingInsts, Unit};
171
172/// A register as the graph keys on it: the number and the file it is in.
173///
174/// The number on its own is not enough. A [`Reg`] that has been through the allocator is a place on
175/// the machine, and a machine numbers the places in each of its files from zero, so the first
176/// integer register and the first vector register are the same number and not the same place. A
177/// graph keyed on the number alone would chain a block's floating point work to the integer work
178/// beside it for no reason, which costs a schedule and is not wrong. A virtual register has one
179/// class for its whole life, so for anything that has not been through the allocator the pair says
180/// exactly what the number alone would.
181type Place = (Reg, RegClass);
182
183/// How many instructions are looked at when choosing the next one.
184///
185/// `gcc/params.opt:761`'s `max-sched-ready-insns`, `Init(100)`, and the same number for the same
186/// reason. The ones looked at are the ones that were earliest in the input, so the bound is a
187/// function of the input like everything else here.
188pub const READY: usize = 100;
189
190/// The longest run of instructions this will schedule.
191///
192/// Building the graph is quadratic in the worst case, since an instruction that writes a register
193/// has to be put behind every instruction that read it. A run longer than this is left exactly as
194/// it arrived.
195pub const LONGEST: usize = 2000;
196
197/// What one function came to.
198#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
199pub struct Scheduled {
200 /// Runs of instructions a schedule was chosen for.
201 pub runs: usize,
202 /// Instructions that came out somewhere other than where they went in.
203 pub moved: usize,
204}
205
206/// Puts each block's instructions in the order the machine finishes soonest.
207///
208/// `accurate` is whether the unit counts in the model are worth holding an instruction back over,
209/// which is `cycle-accurate-model` of section 38.1. A model that is not cycle accurate is one whose
210/// latencies came out of a table and whose picture of the machine's units is a summary, so the
211/// latencies are used to order and the units are not used to stall. See [`TimingInsts::accurate`].
212///
213/// `pinned` is the instructions the caller needs left where they are. The block's own last
214/// instruction is always one, and the caller adds the comparisons [`crate::layout`] is going to
215/// fuse with a branch, which have to stay next to the branch for the fusion to happen.
216pub fn insts(
217 func: &mut Func,
218 timing: &TimingInsts,
219 machine: &MachineInsts,
220 flags: &FlagInsts,
221 names: &Interner,
222 accurate: bool,
223 pinned: &HashSet<Inst>,
224) -> Scheduled {
225 let blocks: Vec<Block> = func.blocks().collect();
226 let mut done = Scheduled::default();
227 let mut known = Known { timing, machine, flags, names, seen: HashMap::new() };
228 for block in blocks {
229 let was: Vec<Inst> = func.insts(block).collect();
230 if was.len() < 3 {
231 continue;
232 }
233 let mut now: Vec<Inst> = Vec::with_capacity(was.len());
234 let mut run: Vec<Inst> = Vec::new();
235 let last = was.last().copied();
236 for &inst in &was {
237 if Some(inst) == last || pinned.contains(&inst) || known.of(func, inst).barrier {
238 done.runs += usize::from(order(func, &run, &mut known, accurate, &mut now));
239 run.clear();
240 now.push(inst);
241 } else {
242 run.push(inst);
243 }
244 }
245 done.runs += usize::from(order(func, &run, &mut known, accurate, &mut now));
246 let moved = was.iter().zip(&now).filter(|(before, after)| before != after).count();
247 if moved == 0 {
248 continue;
249 }
250 done.moved += moved;
251 for &inst in &was {
252 func.remove_inst(inst);
253 }
254 for &inst in &now {
255 func.append_inst(block, inst);
256 }
257 }
258 done
259}
260
261/// What the target says about one opcode, which is the same for every instruction spelled that
262/// way.
263///
264/// Every one of these is a lookup by the opcode's name, and a target's tables are matches on
265/// strings, so asking them for each instruction compared its name against a few hundred others
266/// each time. This pass asked seven such questions of every instruction, and on jtckdint's main,
267/// with 190000 of them, the comparing came to a twentieth of the `-O2` build.
268#[derive(Debug, Clone, Copy)]
269struct Facts {
270 /// Whether the name alone makes it a barrier. A frame rule after it is about the instruction
271 /// rather than the name, so [`Known::of`] asks that one each time.
272 barrier: bool,
273 /// What it costs, which a barrier by name has none of.
274 timing: Option<Timing>,
275 reads_flags: bool,
276 writes_flags: bool,
277 touches_mem: bool,
278}
279
280/// The target's tables, and what they have said so far, by opcode.
281struct Known<'a> {
282 timing: &'a TimingInsts,
283 machine: &'a MachineInsts,
284 flags: &'a FlagInsts,
285 names: &'a Interner,
286 seen: HashMap<Symbol, Facts>,
287}
288
289impl Known<'_> {
290 /// What the target says about this instruction's opcode, and whether nothing may be moved
291 /// across the instruction.
292 ///
293 /// See the module comment. The five barriers are a call, a branch or a return, a name the
294 /// target does not have, a name the target has and the timing model does not cover, and an
295 /// instruction carrying a frame rule.
296 fn of(&mut self, func: &Func, inst: Inst) -> Facts {
297 let symbol = func[inst].opcode.name();
298 let (timing, machine, flags, names) = (self.timing, self.machine, self.flags, self.names);
299 let mut facts = *self.seen.entry(symbol).or_insert_with(|| {
300 let name = names.resolve(symbol);
301 let bare = name.strip_prefix(flags.prefix).unwrap_or(name);
302 let cost = timing.of(name);
303 Facts {
304 barrier: machine.calls(name)
305 || !machine.has(name)
306 || cost.is_none_or(|cost| matches!(cost.unit, Unit::Fixed | Unit::Branch)),
307 timing: cost,
308 reads_flags: flags.reads(bare).is_some(),
309 writes_flags: (flags.writes)(bare),
310 touches_mem: machine.touches_mem(name),
311 }
312 });
313 facts.barrier = facts.barrier || func.cfi_after(inst).next().is_some();
314 facts
315 }
316}
317
318/// Chooses an order for one run and appends it, saying whether there was anything to choose.
319fn order(
320 func: &Func,
321 run: &[Inst],
322 known: &mut Known<'_>,
323 accurate: bool,
324 into: &mut Vec<Inst>,
325) -> bool {
326 if run.len() < 2 || run.len() > LONGEST {
327 into.extend_from_slice(run);
328 return false;
329 }
330 let nodes = graph(func, run, known);
331 into.extend(list(&nodes, known.timing, accurate).into_iter().map(|at| run[at]));
332 true
333}
334
335/// One instruction of a run, and everything the choosing needs to know about it.
336#[derive(Debug)]
337struct Node {
338 /// What it costs, from the target's model.
339 timing: Timing,
340 /// The instructions that may not start before it, and how long each has to wait.
341 ///
342 /// The wait is how long the value takes where the edge is one instruction reading what another
343 /// wrote, and it is nothing where the edge is only about the two staying in order.
344 succs: Vec<(usize, u32)>,
345 /// How many instructions it may not start before, counted down as they are scheduled.
346 preds: usize,
347 /// The longest path from here to the end of the run, in cycles. Criterion one.
348 height: u32,
349 /// How many more registers are live after it than before. Criterion two.
350 ///
351 /// Within the run, so a register that is read here and read again in the next block counts as
352 /// dying here. Being wrong about that changes which of two instructions with the same critical
353 /// path goes first and nothing else, which is what a tiebreak is allowed to be wrong about.
354 growth: i32,
355}
356
357/// Builds the dependence graph of one run.
358fn graph(func: &Func, run: &[Inst], known: &mut Known<'_>) -> Vec<Node> {
359 let facts: Vec<Facts> = run.iter().map(|&inst| known.of(func, inst)).collect();
360 let costs: Vec<Timing> =
361 facts.iter().map(|facts| facts.timing.expect("a barrier otherwise")).collect();
362 let mut nodes: Vec<Node> = costs
363 .iter()
364 .map(|&timing| Node { timing, succs: Vec::new(), preds: 0, height: 0, growth: 0 })
365 .collect();
366
367 // The last instruction to write each register, and every instruction to read one since. The
368 // condition state is the same two questions with nowhere to keep the register's number, since
369 // it is not an operand on a machine that has one.
370 let mut wrote: HashMap<Place, usize> = HashMap::new();
371 let mut read: HashMap<Place, Vec<usize>> = HashMap::new();
372 let mut wrote_flags: Option<usize> = None;
373 let mut read_flags: Vec<usize> = Vec::new();
374 let mut touched: Option<usize> = None;
375
376 for (at, &inst) in run.iter().enumerate() {
377 let facts = facts[at];
378
379 // Reads before writes, because an instruction whose destination is one of its own sources
380 // is on both lists and the write it does is not one its own read has to wait for.
381 for operand in &func[func[inst].operands] {
382 if operand.role == Role::Use {
383 let place = (operand.reg, operand.class);
384 if let Some(before) = wrote.get(&place) {
385 edge(&mut nodes, *before, at, costs[*before].latency);
386 }
387 read.entry(place).or_default().push(at);
388 }
389 }
390 if facts.reads_flags {
391 if let Some(before) = wrote_flags {
392 edge(&mut nodes, before, at, costs[before].latency);
393 }
394 read_flags.push(at);
395 }
396 for operand in &func[func[inst].operands] {
397 if operand.role.is_def() {
398 let place = (operand.reg, operand.class);
399 if let Some(before) = wrote.insert(place, at) {
400 edge(&mut nodes, before, at, after(&costs, before));
401 }
402 for before in read.remove(&place).unwrap_or_default() {
403 if before != at {
404 edge(&mut nodes, before, at, 0);
405 }
406 }
407 }
408 }
409 if facts.writes_flags {
410 if let Some(before) = wrote_flags.replace(at) {
411 edge(&mut nodes, before, at, after(&costs, before));
412 }
413 for before in read_flags.drain(..) {
414 if before != at {
415 edge(&mut nodes, before, at, 0);
416 }
417 }
418 }
419
420 // Memory and addresses, which are one chain. See the module comment.
421 if facts.touches_mem || func[inst].mem.is_some() {
422 if let Some(before) = touched.replace(at) {
423 edge(&mut nodes, before, at, 0);
424 }
425 }
426 }
427
428 heights(&mut nodes);
429 growth(func, run, &mut nodes);
430 nodes
431}
432
433/// Says that the second instruction may not start until that many cycles after the first.
434///
435/// One edge per pair, keeping the longest wait. Two instructions are often joined for several
436/// reasons at once, and what the pair costs is the strongest of the reasons rather than the sum of
437/// them: a multiply whose result the next instruction reads and whose condition state it also
438/// overwrites is one edge of three cycles, not a three cycle edge and a one cycle edge. Keeping one
439/// edge per pair is also what makes criterion seven count instructions rather than reasons.
440fn edge(nodes: &mut [Node], from: usize, to: usize, wait: u32) {
441 if let Some(found) = nodes[from].succs.iter_mut().find(|(succ, _)| *succ == to) {
442 found.1 = found.1.max(wait);
443 return;
444 }
445 nodes[from].succs.push((to, wait));
446 nodes[to].preds += 1;
447}
448
449/// How long after one write of somewhere the next write of the same somewhere may start.
450///
451/// The two have to land in order, and an instruction that takes no time has landed by the time it
452/// has started, so this is a cycle for real work and nothing for the instructions that encode to
453/// nothing. The ones that encode to nothing are the reason it is worth asking: a machine function
454/// opens with an instruction per argument saying which register the argument is already in, each of
455/// them writes a register the real work then writes again, and charging a cycle for that held every
456/// first use of an argument one cycle behind where it could have been.
457fn after(costs: &[Timing], before: usize) -> u32 {
458 costs[before].latency.min(1)
459}
460
461/// The longest path from each instruction to the end of the run.
462///
463/// One pass backwards, which is all it takes because every edge goes from an earlier instruction to
464/// a later one: the graph is built by walking the run forwards and only ever putting an edge from
465/// something already seen to the instruction being looked at.
466fn heights(nodes: &mut [Node]) {
467 for at in (0..nodes.len()).rev() {
468 let mut height = nodes[at].timing.latency;
469 for index in 0..nodes[at].succs.len() {
470 let (succ, wait) = nodes[at].succs[index];
471 height = height.max(wait + nodes[succ].height);
472 }
473 nodes[at].height = height;
474 }
475}
476
477/// How many more registers are live after each instruction than before it.
478///
479/// A register a run reads for the last time is one whose value is not wanted afterwards, so the
480/// instruction that reads it gives a register back. One that writes a register takes one. The
481/// difference is what criterion two compares, and what it is really asking is whether an
482/// instruction is doing work or making something that will have to be kept until later.
483fn growth(func: &Func, run: &[Inst], nodes: &mut [Node]) {
484 let mut seen: HashSet<Place> = HashSet::new();
485 for (at, &inst) in run.iter().enumerate().rev() {
486 for operand in &func[func[inst].operands] {
487 if operand.role == Role::Use && seen.insert((operand.reg, operand.class)) {
488 nodes[at].growth -= 1;
489 }
490 }
491 for operand in &func[func[inst].operands] {
492 if operand.role.is_def() {
493 nodes[at].growth += 1;
494 }
495 }
496 }
497}
498
499/// The five numbers one instruction is chosen by, in the order they are compared.
500///
501/// Derived rather than written out, because the order the fields are in is the order section 38.1
502/// puts the criteria in and keeping the two the same is the point. Every field is one where smaller
503/// is better, so the one that sorts first is the one to schedule.
504#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
505struct Pick {
506 /// Criterion one, negated: the longest path to the end of the run, longest first.
507 path: i64,
508 /// Criterion two: how many registers it leaves live that were not, fewest first.
509 growth: i32,
510 /// Criterion six: whether it reads what was just scheduled, and so has to wait for it.
511 waits: bool,
512 /// Criterion seven, negated: how many instructions depend on it, most first.
513 users: i64,
514 /// Criterion eight: where it was in the input, earliest first.
515 at: usize,
516}
517
518/// Chooses an order, as positions into the run.
519fn list(nodes: &[Node], timing: &TimingInsts, accurate: bool) -> Vec<usize> {
520 let mut preds: Vec<usize> = nodes.iter().map(|node| node.preds).collect();
521 let mut when: Vec<u32> = vec![0; nodes.len()];
522 let mut ready: BTreeSet<usize> = (0..nodes.len()).filter(|&at| preds[at] == 0).collect();
523 let mut out: Vec<usize> = Vec::with_capacity(nodes.len());
524 let mut cycle = 0;
525 let mut used: HashMap<Unit, u32> = HashMap::new();
526 let mut issued = 0;
527 let mut last: Option<usize> = None;
528
529 while !ready.is_empty() {
530 let mut best: Option<Pick> = None;
531 for &at in ready.iter().take(READY) {
532 if when[at] > cycle || (accurate && !fits(nodes[at].timing.unit, &used, issued, timing))
533 {
534 continue;
535 }
536 let pick = Pick {
537 path: -i64::from(nodes[at].height),
538 growth: nodes[at].growth,
539 waits: last.is_some_and(|last| nodes[last].succs.iter().any(|&(to, _)| to == at)),
540 users: -(nodes[at].succs.len() as i64),
541 at,
542 };
543 if best.is_none_or(|best| pick < best) {
544 best = Some(pick);
545 }
546 }
547 let Some(best) = best else {
548 // Nothing can start this cycle, either because everything ready is still waiting on a
549 // value or because the units it wants are full. Both are answered by the next cycle,
550 // and jumping straight to the one something is ready in keeps a long latency from being
551 // walked over one cycle at a time.
552 let soonest = ready.iter().take(READY).map(|&at| when[at]).min().unwrap_or(cycle);
553 cycle = soonest.max(cycle + 1);
554 used.clear();
555 issued = 0;
556 continue;
557 };
558 let at = best.at;
559 ready.remove(&at);
560 out.push(at);
561 last = Some(at);
562 *used.entry(nodes[at].timing.unit).or_default() += 1;
563 issued += 1;
564 for index in 0..nodes[at].succs.len() {
565 let (succ, wait) = nodes[at].succs[index];
566 when[succ] = when[succ].max(cycle + wait);
567 preds[succ] -= 1;
568 if preds[succ] == 0 {
569 ready.insert(succ);
570 }
571 }
572 }
573 out
574}
575
576/// Whether the machine has room this cycle for an instruction on that unit.
577fn fits(unit: Unit, used: &HashMap<Unit, u32>, issued: u32, timing: &TimingInsts) -> bool {
578 issued < timing.width.max(1) && used.get(&unit).copied().unwrap_or(0) < timing.slots(unit)
579}
580
581#[cfg(test)]
582mod tests {
583 use rucc_mir::{Constraint, Mem, Opcode, Operand};
584 use rucc_target::PhysReg;
585 use rucc_target::x86_64::{
586 self, FLAGS, GPR, MACHINE, R8, R9, R10, RAX, RCX, RDI, RDX, RSI, TIMING, XMM,
587 };
588
589 use super::*;
590
591 /// A function with one block, and the names it was built with.
592 fn empty() -> (Interner, Func, Block) {
593 let mut names = Interner::new();
594 let mut func = Func::new(names.intern("f"));
595 let block = func.create_block();
596 (names, func, block)
597 }
598
599 /// The opcode of that name on this target.
600 fn op(names: &mut Interner, name: &str) -> Opcode {
601 Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
602 }
603
604 /// A register the allocator has already handed out, which is all this pass ever sees.
605 fn reg(which: PhysReg) -> Reg {
606 Reg::physical(which)
607 }
608
609 /// Two address arithmetic writing one of its own sources, which is the shape this machine's
610 /// arithmetic has by the time the allocator has been through it.
611 fn alu(
612 func: &mut Func,
613 names: &mut Interner,
614 block: Block,
615 name: &str,
616 into: PhysReg,
617 from: PhysReg,
618 ) {
619 let opcode = op(names, name);
620 func.build(block, opcode)
621 .operand(Operand::write(reg(into), GPR).with(Constraint::Reuse(1)))
622 .uses(reg(into), GPR)
623 .uses(reg(from), GPR)
624 .finish();
625 }
626
627 /// The same, on the vector registers.
628 fn vector(
629 func: &mut Func,
630 names: &mut Interner,
631 block: Block,
632 name: &str,
633 into: PhysReg,
634 from: PhysReg,
635 ) {
636 let opcode = op(names, name);
637 func.build(block, opcode)
638 .operand(Operand::write(reg(into), XMM).with(Constraint::Reuse(1)))
639 .uses(reg(into), XMM)
640 .uses(reg(from), XMM)
641 .finish();
642 }
643
644 /// A move of one register into another.
645 fn mov(func: &mut Func, names: &mut Interner, block: Block, into: PhysReg, from: PhysReg) {
646 let opcode = op(names, "mov_rr_64");
647 func.build(block, opcode).def(reg(into), GPR).uses(reg(from), GPR).finish();
648 }
649
650 /// An eight byte read off that register.
651 fn load(func: &mut Func, names: &mut Interner, block: Block, into: PhysReg, base: PhysReg) {
652 let opcode = op(names, "mov_rm_64");
653 func.build(block, opcode)
654 .def(reg(into), GPR)
655 .mem(Mem::at(Operand::read(reg(base), GPR)))
656 .finish();
657 }
658
659 /// An instruction of that name with no operands at all, which is what a call, a fence and a
660 /// return are on this machine.
661 fn bare(func: &mut Func, names: &mut Interner, block: Block, name: &str) {
662 let opcode = op(names, name);
663 func.build(block, opcode).finish();
664 }
665
666 /// What every instruction in a block came to, as opcodes with the target's prefix taken off.
667 fn shape(func: &Func, names: &Interner, block: Block) -> Vec<String> {
668 func.insts(block)
669 .map(|inst| TIMING.bare(names.resolve(func[inst].opcode.name())).to_owned())
670 .collect()
671 }
672
673 /// The pass, with nothing pinned beyond the block's own last instruction.
674 fn schedule(func: &mut Func, names: &Interner) -> Scheduled {
675 insts(func, &TIMING, &MACHINE, &FLAGS, names, false, &HashSet::new())
676 }
677
678 /// A chain of three where only one order computes the right answer.
679 #[test]
680 fn a_block_already_in_the_only_order_it_has_comes_out_unchanged() {
681 let (mut names, mut func, block) = empty();
682 mov(&mut func, &mut names, block, RAX, RDX);
683 alu(&mut func, &mut names, block, "add_rr_64", RAX, RCX);
684 bare(&mut func, &mut names, block, "ret");
685
686 let done = schedule(&mut func, &names);
687 assert_eq!(done.moved, 0, "there was nothing else it could have written");
688 assert_eq!(shape(&func, &names, block), ["mov_rr_64", "add_rr_64", "ret"]);
689 }
690
691 /// The shape the whole pass is for: a multiply takes three cycles and the instruction that reads
692 /// it has to wait for all three, so work that was behind both of them is put in the middle.
693 #[test]
694 fn work_that_depends_on_nothing_moves_into_a_multiplys_latency() {
695 let (mut names, mut func, block) = empty();
696 alu(&mut func, &mut names, block, "imul_rr_64", RDI, RSI);
697 alu(&mut func, &mut names, block, "add_rr_64", RDI, RCX);
698 mov(&mut func, &mut names, block, RAX, RDX);
699 bare(&mut func, &mut names, block, "ret");
700
701 let done = schedule(&mut func, &names);
702 assert_eq!(done.runs, 1, "one run, since nothing in it is a barrier");
703 assert_eq!(
704 shape(&func, &names, block),
705 ["imul_rr_64", "mov_rr_64", "add_rr_64", "ret"],
706 "the move is doing a cycle of the three the addition was going to spend waiting"
707 );
708 }
709
710 /// A call, which is the barrier the module comment puts first. Without it the multiply below
711 /// would be hoisted over the call, since it has the longer path and nothing in its operands says
712 /// a call is in the way.
713 #[test]
714 fn nothing_crosses_a_call() {
715 let (mut names, mut func, block) = empty();
716 mov(&mut func, &mut names, block, RAX, RDX);
717 bare(&mut func, &mut names, block, "call");
718 alu(&mut func, &mut names, block, "imul_rr_64", RDI, RSI);
719 alu(&mut func, &mut names, block, "add_rr_64", RDI, RCX);
720 bare(&mut func, &mut names, block, "ret");
721
722 let done = schedule(&mut func, &names);
723 assert_eq!(done.moved, 0);
724 assert_eq!(
725 shape(&func, &names, block),
726 ["mov_rr_64", "call", "imul_rr_64", "add_rr_64", "ret"]
727 );
728 }
729
730 /// Two reads of memory. The second one starts a chain with a longer path than the first, so the
731 /// only thing keeping them in order is that they both touch memory.
732 #[test]
733 fn two_reads_of_memory_keep_the_order_they_arrived_in() {
734 let (mut names, mut func, block) = empty();
735 load(&mut func, &mut names, block, RAX, RDI);
736 load(&mut func, &mut names, block, RCX, RSI);
737 alu(&mut func, &mut names, block, "imul_rr_64", RCX, RDX);
738 bare(&mut func, &mut names, block, "ret");
739
740 let done = schedule(&mut func, &names);
741 assert_eq!(done.moved, 0);
742 assert_eq!(
743 shape(&func, &names, block),
744 ["mov_rm_64", "mov_rm_64", "imul_rr_64", "ret"],
745 "the read whose value nothing here wants stayed in front of the one that matters"
746 );
747 }
748
749 /// Two writes of one register, where the first one's value is never read. What decides the
750 /// register's contents afterwards is which of them ran last.
751 #[test]
752 fn two_writes_of_one_register_keep_the_order_they_arrived_in() {
753 let (mut names, mut func, block) = empty();
754 mov(&mut func, &mut names, block, RAX, RDX);
755 mov(&mut func, &mut names, block, RAX, RCX);
756 alu(&mut func, &mut names, block, "imul_rr_64", RAX, RSI);
757 bare(&mut func, &mut names, block, "ret");
758
759 let done = schedule(&mut func, &names);
760 assert_eq!(done.moved, 0);
761 assert_eq!(shape(&func, &names, block), ["mov_rr_64", "mov_rr_64", "imul_rr_64", "ret"]);
762 }
763
764 /// A write of a register something in front of it reads. No value passes between the two, and
765 /// the order between them is still the difference between right and wrong.
766 #[test]
767 fn a_write_stays_behind_the_read_of_what_the_register_held() {
768 let (mut names, mut func, block) = empty();
769 alu(&mut func, &mut names, block, "add_rr_64", RCX, RAX);
770 mov(&mut func, &mut names, block, RAX, RDX);
771 alu(&mut func, &mut names, block, "imul_rr_64", RAX, RSI);
772 bare(&mut func, &mut names, block, "ret");
773
774 let done = schedule(&mut func, &names);
775 assert_eq!(done.moved, 0);
776 assert_eq!(
777 shape(&func, &names, block),
778 ["add_rr_64", "mov_rr_64", "imul_rr_64", "ret"],
779 "the addition read what was in the register before the move put something else there"
780 );
781 }
782
783 /// The condition state, which is in no operand vector. The instruction that reads it has the
784 /// longer path of the two ready at the start, so if the target's answer about the flags were not
785 /// being used it would be scheduled first.
786 #[test]
787 fn the_instruction_that_reads_the_condition_state_stays_behind_the_comparison() {
788 let (mut names, mut func, block) = empty();
789 mov(&mut func, &mut names, block, RCX, RDX);
790 let cmp = op(&mut names, "cmp_rr_64");
791 func.build(block, cmp).uses(reg(RDI), GPR).uses(reg(RSI), GPR).finish();
792 let set = op(&mut names, "set_e");
793 func.build(block, set).def(reg(RAX), GPR).finish();
794 alu(&mut func, &mut names, block, "add_rr_64", RAX, R8);
795 bare(&mut func, &mut names, block, "ret");
796
797 schedule(&mut func, &names);
798 assert_eq!(
799 shape(&func, &names, block),
800 ["cmp_rr_64", "mov_rr_64", "set_e", "add_rr_64", "ret"],
801 "the move went into the cycle the set was waiting for the comparison in"
802 );
803 }
804
805 /// The block's own last instruction, which [`crate::layout`] needs where it is.
806 #[test]
807 fn the_last_instruction_of_a_block_never_moves() {
808 let (mut names, mut func, block) = empty();
809 mov(&mut func, &mut names, block, RAX, RDX);
810 mov(&mut func, &mut names, block, RCX, R8);
811 alu(&mut func, &mut names, block, "imul_rr_64", RSI, R9);
812
813 let done = schedule(&mut func, &names);
814 assert_eq!(done.moved, 0);
815 assert_eq!(
816 shape(&func, &names, block),
817 ["mov_rr_64", "mov_rr_64", "imul_rr_64"],
818 "the multiply has the longest path and is last anyway"
819 );
820 }
821
822 /// What the caller pins, which is the comparison the layout is going to fuse with a branch.
823 #[test]
824 fn an_instruction_the_caller_pinned_never_moves() {
825 let build = |names: &mut Interner| {
826 let mut func = Func::new(names.intern("f"));
827 let block = func.create_block();
828 mov(&mut func, names, block, RAX, RDX);
829 mov(&mut func, names, block, RCX, R8);
830 alu(&mut func, names, block, "imul_rr_64", RSI, R9);
831 bare(&mut func, names, block, "ret");
832 (func, block)
833 };
834
835 let mut names = Interner::new();
836 let (mut loose, block) = build(&mut names);
837 schedule(&mut loose, &names);
838 assert_eq!(
839 shape(&loose, &names, block),
840 ["imul_rr_64", "mov_rr_64", "mov_rr_64", "ret"],
841 "with nothing pinned the multiply goes first, since it has the longest path"
842 );
843
844 let (mut held, block) = build(&mut names);
845 let second = held.insts(block).nth(1).expect("the second move");
846 insts(&mut held, &TIMING, &MACHINE, &FLAGS, &names, false, &HashSet::from([second]));
847 assert_eq!(
848 shape(&held, &names, block),
849 ["mov_rr_64", "mov_rr_64", "imul_rr_64", "ret"],
850 "pinning it splits the block into runs of one, and a run of one has one order"
851 );
852 }
853
854 /// A name the target does not have, which is the barrier that keeps a rule set growing an opcode
855 /// from quietly growing a wrong schedule.
856 #[test]
857 fn a_name_this_target_does_not_have_is_a_barrier() {
858 let (mut names, mut func, block) = empty();
859 mov(&mut func, &mut names, block, RAX, RDX);
860 bare(&mut func, &mut names, block, "not_an_instruction_this_machine_has");
861 alu(&mut func, &mut names, block, "imul_rr_64", RSI, R9);
862 bare(&mut func, &mut names, block, "ret");
863
864 let done = schedule(&mut func, &names);
865 assert_eq!(done.moved, 0);
866 assert_eq!(
867 shape(&func, &names, block),
868 ["mov_rr_64", "not_an_instruction_this_machine_has", "imul_rr_64", "ret"]
869 );
870 }
871
872 /// A trap, which the target has and the timing model deliberately does not describe.
873 #[test]
874 fn an_instruction_the_model_does_not_describe_is_a_barrier() {
875 let (mut names, mut func, block) = empty();
876 mov(&mut func, &mut names, block, RAX, RDX);
877 bare(&mut func, &mut names, block, "ud2");
878 alu(&mut func, &mut names, block, "imul_rr_64", RSI, R9);
879 bare(&mut func, &mut names, block, "ret");
880
881 assert_eq!(TIMING.of("x64.ud2").expect("described").unit, Unit::Fixed);
882 let done = schedule(&mut func, &names);
883 assert_eq!(done.moved, 0);
884 assert_eq!(shape(&func, &names, block), ["mov_rr_64", "ud2", "imul_rr_64", "ret"]);
885 }
886
887 /// A return in the middle of a block, which only an `asm` template writes. It reads `rax`
888 /// without naming it, so the `mov` in front of it has nothing tying it there but this.
889 #[test]
890 fn a_return_an_asm_template_wrote_is_a_barrier() {
891 let (mut names, mut func, block) = empty();
892 mov(&mut func, &mut names, block, RAX, RDX);
893 bare(&mut func, &mut names, block, "ret");
894 alu(&mut func, &mut names, block, "imul_rr_64", RSI, R9);
895 bare(&mut func, &mut names, block, "ud2");
896
897 assert_eq!(TIMING.of("x64.ret").expect("described").unit, Unit::Branch);
898 let done = schedule(&mut func, &names);
899 assert_eq!(done.moved, 0);
900 assert_eq!(shape(&func, &names, block), ["mov_rr_64", "ret", "imul_rr_64", "ud2"]);
901 }
902
903 /// The property that holds whatever the model says, since the model chooses among orders and
904 /// does not choose what is in one.
905 #[test]
906 fn what_comes_out_is_the_instructions_that_went_in_and_no_others() {
907 let (mut names, mut func, block) = empty();
908 alu(&mut func, &mut names, block, "imul_rr_64", RDI, RSI);
909 mov(&mut func, &mut names, block, RAX, RDX);
910 load(&mut func, &mut names, block, RCX, R8);
911 alu(&mut func, &mut names, block, "add_rr_64", RAX, RCX);
912 alu(&mut func, &mut names, block, "sub_rr_64", RDX, R9);
913 mov(&mut func, &mut names, block, R10, RDI);
914 alu(&mut func, &mut names, block, "imul_rr_64", R10, RAX);
915 alu(&mut func, &mut names, block, "add_rr_64", R10, RDX);
916 bare(&mut func, &mut names, block, "ret");
917 let mut was: Vec<Inst> = func.insts(block).collect();
918
919 schedule(&mut func, &names);
920 let mut now: Vec<Inst> = func.insts(block).collect();
921 assert_eq!(now.len(), was.len(), "nothing was added or dropped");
922 was.sort_unstable();
923 now.sort_unstable();
924 assert_eq!(now, was, "the same instructions, in some order");
925 }
926
927 /// The output is a function of the input. Two hash maps in one process do not agree about the
928 /// order they hand their contents back in, so anything in here that walked one would show up
929 /// here rather than as a program that comes out differently on somebody else's machine.
930 #[test]
931 fn the_same_block_twice_gives_the_same_order_twice() {
932 let build = |names: &mut Interner| {
933 let mut func = Func::new(names.intern("f"));
934 let block = func.create_block();
935 alu(&mut func, names, block, "imul_rr_64", RDI, RSI);
936 mov(&mut func, names, block, RAX, RDX);
937 load(&mut func, names, block, RCX, R8);
938 alu(&mut func, names, block, "add_rr_64", RAX, RCX);
939 alu(&mut func, names, block, "sub_rr_64", RDX, R9);
940 mov(&mut func, names, block, R10, RDI);
941 alu(&mut func, names, block, "imul_rr_64", R10, RAX);
942 bare(&mut func, names, block, "ret");
943 (func, block)
944 };
945
946 let mut names = Interner::new();
947 let (mut first, one) = build(&mut names);
948 let (mut second, two) = build(&mut names);
949 schedule(&mut first, &names);
950 schedule(&mut second, &names);
951 assert_eq!(shape(&first, &names, one), shape(&second, &names, two));
952 }
953
954 /// Criterion two. Both of these are ready at the start and both are the same distance from the
955 /// end, and the one that hands a register back goes first.
956 #[test]
957 fn a_constant_put_in_a_register_is_not_hoisted_over_work_that_hands_one_back() {
958 let (mut names, mut func, block) = empty();
959 let load_imm = op(&mut names, "mov_ri_64");
960 func.build(block, load_imm).def(reg(RCX), GPR).imm(5).finish();
961 alu(&mut func, &mut names, block, "add_rr_64", RAX, RDX);
962 alu(&mut func, &mut names, block, "add_rr_64", RAX, RCX);
963 bare(&mut func, &mut names, block, "ret");
964
965 schedule(&mut func, &names);
966 assert_eq!(
967 shape(&func, &names, block),
968 ["add_rr_64", "mov_ri_64", "add_rr_64", "ret"],
969 "the constant is loaded as early as necessary and no earlier"
970 );
971 }
972
973 /// What [`TimingInsts::accurate`] is for. Three vector additions want the two floating point
974 /// units, and a model worth believing about its units holds the third back and fills the cycle
975 /// with the move instead.
976 #[test]
977 fn a_model_worth_believing_about_its_units_fills_a_full_cycle_with_other_work() {
978 let build = |names: &mut Interner| {
979 let mut func = Func::new(names.intern("f"));
980 let block = func.create_block();
981 vector(&mut func, names, block, "addsd_rr", x86_64::xmm(0), x86_64::xmm(1));
982 vector(&mut func, names, block, "addsd_rr", x86_64::xmm(2), x86_64::xmm(3));
983 vector(&mut func, names, block, "addsd_rr", x86_64::xmm(4), x86_64::xmm(5));
984 mov(&mut func, names, block, RAX, RDX);
985 bare(&mut func, names, block, "ret");
986 (func, block)
987 };
988
989 assert_eq!(TIMING.slots(Unit::Float), 2, "the machine this model describes has two");
990
991 let mut names = Interner::new();
992 let (mut loose, block) = build(&mut names);
993 insts(&mut loose, &TIMING, &MACHINE, &FLAGS, &names, false, &HashSet::new());
994 assert_eq!(
995 shape(&loose, &names, block),
996 ["addsd_rr", "addsd_rr", "addsd_rr", "mov_rr_64", "ret"],
997 "without the units the three additions are the same instruction three times over"
998 );
999
1000 let (mut tight, block) = build(&mut names);
1001 insts(&mut tight, &TIMING, &MACHINE, &FLAGS, &names, true, &HashSet::new());
1002 assert_eq!(
1003 shape(&tight, &names, block),
1004 ["addsd_rr", "addsd_rr", "mov_rr_64", "addsd_rr", "ret"],
1005 "the third addition has nowhere to go this cycle and the move has"
1006 );
1007 }
1008
1009 /// The bound, and the same block below it as the control. A run of a few thousand machine
1010 /// instructions is a generated table rather than something anybody is waiting on the schedule
1011 /// of, and building the graph for one is quadratic in the worst case.
1012 ///
1013 /// The moves all write the same register, so they are a chain that has to run in the order it
1014 /// is in and the first of them is further from the end of the run than a three cycle multiply
1015 /// is. Below the bound that is what decides the order. Above it nothing decides anything.
1016 #[test]
1017 fn a_run_longer_than_the_bound_is_left_alone() {
1018 let build = |names: &mut Interner, moves: usize| {
1019 let mut func = Func::new(names.intern("f"));
1020 let block = func.create_block();
1021 alu(&mut func, names, block, "imul_rr_64", RSI, R9);
1022 for _ in 0..moves {
1023 mov(&mut func, names, block, RAX, RDX);
1024 }
1025 bare(&mut func, names, block, "ret");
1026 (func, block)
1027 };
1028
1029 let mut names = Interner::new();
1030 let (mut short, block) = build(&mut names, 8);
1031 let done = schedule(&mut short, &names);
1032 assert!(done.moved > 0, "below the bound a run is looked at");
1033 assert_eq!(
1034 shape(&short, &names, block).first().map(String::as_str),
1035 Some("mov_rr_64"),
1036 "the chain of moves is the long way round and starts first"
1037 );
1038
1039 let (mut long, block) = build(&mut names, LONGEST);
1040 let done = schedule(&mut long, &names);
1041 assert_eq!(done.moved, 0, "above it the run is written back exactly as it arrived");
1042 assert_eq!(shape(&long, &names, block).first().map(String::as_str), Some("imul_rr_64"));
1043 }
1044
1045 /// A block too short to have anything to choose, which is the one case the pass skips outright.
1046 #[test]
1047 fn a_block_of_two_instructions_is_not_looked_at() {
1048 let (mut names, mut func, block) = empty();
1049 alu(&mut func, &mut names, block, "imul_rr_64", RSI, R9);
1050 bare(&mut func, &mut names, block, "ret");
1051
1052 let done = schedule(&mut func, &names);
1053 assert_eq!(done, Scheduled::default());
1054 assert_eq!(shape(&func, &names, block), ["imul_rr_64", "ret"]);
1055 }
1056
1057 /// A shift by a variable amount, which the machine takes out of one particular register and
1058 /// this target's description names as an operand with that register fixed. The whole of this
1059 /// pass reads operand vectors, so an instruction whose description left a register it touches
1060 /// out of one would be reordered around a write of it. This is the check that it does not.
1061 #[test]
1062 fn a_shift_by_a_variable_amount_stays_behind_the_write_of_the_register_it_counts() {
1063 let (mut names, mut func, block) = empty();
1064 mov(&mut func, &mut names, block, RCX, R8);
1065 let shift = op(&mut names, "shl_rcl_64");
1066 func.build(block, shift)
1067 .operand(Operand::write(reg(RAX), GPR).with(Constraint::Reuse(1)))
1068 .uses(reg(RAX), GPR)
1069 .uses(reg(RCX), GPR)
1070 .finish();
1071 alu(&mut func, &mut names, block, "imul_rr_64", RAX, RDX);
1072 bare(&mut func, &mut names, block, "ret");
1073
1074 let done = schedule(&mut func, &names);
1075 assert_eq!(done.moved, 0);
1076 assert_eq!(shape(&func, &names, block), ["mov_rr_64", "shl_rcl_64", "imul_rr_64", "ret"]);
1077 }
1078
1079 /// A divide, which reads and writes two particular registers and names all four of them. It is
1080 /// twenty six cycles from the end of this run and the move in front of it is one, so the only
1081 /// thing keeping it where it is is that it said it writes the register the move writes.
1082 #[test]
1083 fn a_divide_names_both_of_the_registers_the_machine_makes_it_use() {
1084 let (mut names, mut func, block) = empty();
1085 mov(&mut func, &mut names, block, RDX, R8);
1086 let divide = op(&mut names, "idiv_quo_64");
1087 func.build(block, divide)
1088 .operand(Operand::write(reg(RAX), GPR).with(Constraint::Fixed(RAX)))
1089 .operand(Operand::write_early(reg(RDX), GPR).with(Constraint::Fixed(RDX)))
1090 .operand(Operand::read(reg(RAX), GPR).with(Constraint::Fixed(RAX)))
1091 .uses(reg(RSI), GPR)
1092 .finish();
1093 bare(&mut func, &mut names, block, "ret");
1094
1095 assert!(TIMING.of("x64.idiv_quo_64").expect("described").latency > 1);
1096 let done = schedule(&mut func, &names);
1097 assert_eq!(done.moved, 0);
1098 assert_eq!(shape(&func, &names, block), ["mov_rr_64", "idiv_quo_64", "ret"]);
1099 }
1100
1101 /// Every unit the model has, reached through an instruction that is on it, since a unit nothing
1102 /// can get a slot on is a scheduler that does not finish.
1103 #[test]
1104 fn every_unit_a_run_can_ask_for_has_at_least_one_of_it() {
1105 for &unit in Unit::ALL {
1106 assert!(TIMING.slots(unit) >= 1, "{unit:?} has none of it");
1107 }
1108 }
1109
1110 /// Two files that each number their registers from zero. The move and the addition here both
1111 /// write the register numbered nothing, and they are not writing the same register: one is the
1112 /// first integer register and the other is the first vector register. See [`Place`].
1113 #[test]
1114 fn the_first_register_of_each_file_is_not_the_same_register() {
1115 let (mut names, mut func, block) = empty();
1116 mov(&mut func, &mut names, block, RAX, RDX);
1117 vector(&mut func, &mut names, block, "addsd_rr", x86_64::xmm(0), x86_64::xmm(1));
1118 bare(&mut func, &mut names, block, "ret");
1119
1120 assert_eq!(reg(RAX), reg(x86_64::xmm(0)), "and a register on its own does not say which");
1121 schedule(&mut func, &names);
1122 assert_eq!(
1123 shape(&func, &names, block),
1124 ["addsd_rr", "mov_rr_64", "ret"],
1125 "the addition is four cycles from the end and the move is one, and nothing joins them"
1126 );
1127 }
1128}