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}