Skip to main content

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}