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