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