Skip to main content

rucc_regalloc/
assign.rs

1//! Which register each value lives in, and which values live on the stack instead.
2//!
3//! Design: `spec/10-backend.md` section 10.4.
4//!
5//! This is the `-O0` allocator's decision and nothing else. It is linear scan over the line
6//! [`crate::order`] lays the function out in: the values are taken in the order they are written,
7//! each is given a register that nothing else live at the same time is in, and when there is no
8//! such register one of the values in flight goes to the stack instead. There is no splitting and
9//! no coalescing, so a value gets one place for the whole of its range and keeps it. That produces
10//! mediocre code quickly, which is what `-O0` is for, and the allocator that produces good code
11//! slowly is a separate one, in M4.
12//!
13//! Which value is sent to the stack is the one whose range ends last, counting the value being
14//! placed among the candidates. A value wanted for a long time is the cheapest to spill per
15//! instruction it frees a register over, and it is the only heuristic here.
16//!
17//! # What it does with a register an instruction insists on
18//!
19//! Nothing, except stay out of it. A division wants its dividend in `rax`, and the answer here is
20//! not to give the dividend `rax` for the whole of its life. It is to leave `rax` free at that one
21//! instruction, so that a move can put the value there on the way in. That costs a move the
22//! backtracking allocator will not need, and it buys one rule that holds everywhere: an operand
23//! with a fixed register is a fact about the instruction, not about the value in it, so it makes
24//! that register unavailable to everything across that instruction rather than claiming a value.
25//!
26//! An operand that has to be in memory is the other way round. The value it names goes on the
27//! stack whatever else is true of it, because that is the only place the instruction could read it
28//! from.
29//!
30//! # What it does with a two address instruction
31//!
32//! An `add` on x86-64 writes one of the registers it reads, which the operand says as a reuse of
33//! another operand. The rewrite can always make that true by copying the source into the
34//! destination first, but only if the destination is a register the instruction does not otherwise
35//! read, so a value written by a reuse is treated here as live from where the instruction reads
36//! rather than from where it writes. Then the copy is always safe.
37//!
38//! The copy is also usually unnecessary, and the one place this looks past the interval it is
39//! placing is to see that: if the value being reused is read here for the last time, the value
40//! being written may have its register, and the instruction is already two address without
41//! anything being moved anywhere. That is the whole of the coalescing this allocator does, and it
42//! is worth the dozen lines, because otherwise every piece of arithmetic in the output carries a
43//! move in front of it.
44//!
45//! # What it does not do
46//!
47//! It does not touch the function. What comes out is a table saying where each value went, and the
48//! pass that rewrites the operands and writes the moves reads it. Keeping the decision and the
49//! rewrite apart is what lets the decision be checked by looking at it, and it is the shape
50//! `spec/10-backend.md` section 10.4 asks for: an allocator is a function from a program to an
51//! assignment and the moves that make it true.
52
53use rucc_mir::{Constraint, Func, Reg};
54use rucc_target::{PhysReg, RegClass};
55
56use crate::live::{Live, Range};
57use crate::order::{Order, Point};
58
59/// Where a value lives.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum Place {
62    /// In a register, for the whole of its range.
63    Reg(PhysReg),
64    /// In a slot of the frame, which is what a value the allocator ran out of registers for gets,
65    /// and what a value an instruction can only read from memory gets.
66    Slot(u32),
67}
68
69/// What the allocator is allowed to use.
70///
71/// The order is the calling convention's, because which register to hand out first follows from
72/// which ones a call destroys, and `rucc-target` is where a convention says so. The scratch
73/// registers are held back out of the order and are what a spilled value is read into at each
74/// instruction that wants it, so a class needs as many of them as one of its instructions has
75/// register operands. Nothing here uses them, since a spilled value is only read once the rewrite
76/// is writing the instruction that reads it, but they are held back here because this is what
77/// decides what everything else may have.
78#[derive(Debug, Clone, Default)]
79pub struct Env {
80    classes: Vec<Class>,
81}
82
83/// What one class of registers offers.
84#[derive(Debug, Clone, Default)]
85struct Class {
86    order: Vec<PhysReg>,
87    scratch: Vec<PhysReg>,
88}
89
90impl Env {
91    /// An environment offering nothing, which is what a target that has said nothing offers.
92    #[must_use]
93    pub fn new() -> Self {
94        Self::default()
95    }
96
97    /// The same environment, with that class described.
98    #[must_use]
99    pub fn with(mut self, class: RegClass, order: &[PhysReg], scratch: &[PhysReg]) -> Self {
100        let index = usize::from(class.number());
101        if self.classes.len() <= index {
102            self.classes.resize(index + 1, Class::default());
103        }
104        self.classes[index] = Class { order: order.to_vec(), scratch: scratch.to_vec() };
105        self
106    }
107
108    /// The registers it may hand out in a class, in the order it prefers them.
109    #[must_use]
110    pub fn order(&self, class: RegClass) -> &[PhysReg] {
111        self.classes.get(usize::from(class.number())).map_or(&[], |class| &class.order)
112    }
113
114    /// The registers held back in a class for reading a spilled value into.
115    #[must_use]
116    pub fn scratch(&self, class: RegClass) -> &[PhysReg] {
117        self.classes.get(usize::from(class.number())).map_or(&[], |class| &class.scratch)
118    }
119}
120
121/// Where every value in a function went.
122#[derive(Debug, Clone)]
123pub struct Assignment {
124    places: Vec<Option<Place>>,
125    slots: Vec<RegClass>,
126}
127
128impl Assignment {
129    /// An assignment that says nothing yet about a function with that many values.
130    ///
131    /// This and [`Assignment::put`] and [`Assignment::take_slot`] are how an allocator says what
132    /// it decided. There will be a second one in M4 and it will not reach its answer this way, so
133    /// what an assignment is has to be separable from how this file arrives at one, and the
134    /// checker in [`crate::check`] reads an assignment without caring which allocator wrote it.
135    #[must_use]
136    pub fn empty(vregs: usize) -> Self {
137        Self { places: vec![None; vregs], slots: Vec::new() }
138    }
139
140    /// Records where a value went.
141    ///
142    /// # Panics
143    ///
144    /// Panics on a physical register, which is somewhere already, and on a virtual one the
145    /// function never handed out.
146    pub fn put(&mut self, reg: Reg, place: Place) {
147        self.places[index(reg)] = Some(place);
148    }
149
150    /// Takes a slot of the frame, of that class, and gives back which one it is.
151    ///
152    /// # Panics
153    ///
154    /// Panics past four billion slots, which is a frame no machine has room for.
155    pub fn take_slot(&mut self, class: RegClass) -> u32 {
156        let slot = u32::try_from(self.slots.len()).expect("too many spilled values");
157        self.slots.push(class);
158        slot
159    }
160
161    /// Where a value lives, or `None` for a virtual register this function never mentions and for
162    /// a physical one, which is already where it is.
163    #[must_use]
164    pub fn place(&self, reg: Reg) -> Option<Place> {
165        self.places.get(usize::try_from(reg.number()?).ok()?).copied().flatten()
166    }
167
168    /// The class of each slot of the frame, which is what says how wide it has to be.
169    #[must_use]
170    pub fn slots(&self) -> &[RegClass] {
171        &self.slots
172    }
173
174    /// How many values went to the stack.
175    #[must_use]
176    pub fn spilled(&self) -> usize {
177        self.slots.len()
178    }
179
180    /// Puts a value on the stack, in a slot of its own.
181    fn spill(&mut self, reg: Reg, class: RegClass) {
182        let slot = self.take_slot(class);
183        self.put(reg, Place::Slot(slot));
184    }
185}
186
187/// One value waiting for a place.
188#[derive(Debug, Clone, Copy)]
189struct Interval {
190    reg: Reg,
191    class: RegClass,
192    range: Range,
193}
194
195/// One value that has a register, for as long as it still wants it.
196#[derive(Debug, Clone, Copy)]
197struct Held {
198    reg: Reg,
199    class: RegClass,
200    range: Range,
201    at: PhysReg,
202}
203
204/// A register an instruction insists on, and where it insists on it.
205#[derive(Debug, Clone, Copy)]
206struct Blocked {
207    class: RegClass,
208    at: PhysReg,
209    range: Range,
210}
211
212/// A value written into the register another operand of the same instruction was read from.
213#[derive(Debug, Clone, Copy)]
214struct Reuse {
215    /// The value being read, which is the one whose register would do.
216    source: Reg,
217    /// Where the instruction reads it.
218    at: Point,
219}
220
221/// Decides where every value in a function lives.
222///
223/// # Panics
224///
225/// Panics if a class has no registers to hand out and something in the function is in that class,
226/// since that is a target description that does not describe the target the function is for.
227#[must_use]
228pub fn assign(func: &Func, order: &Order, live: &Live, env: &Env) -> Assignment {
229    let blocked = blocked(func, order);
230    let forced = forced(func);
231    let reuses = reuses(func, order);
232
233    let mut intervals = Vec::with_capacity(func.vregs());
234    for (number, reuse) in reuses.iter().enumerate() {
235        let reg = Reg::virtual_reg(u32::try_from(number).expect("a register number"));
236        let (Some(mut range), Some(class)) = (live.range(reg), func.class_of(reg)) else {
237            continue;
238        };
239        if let Some(reuse) = reuse {
240            range.start = range.start.min(reuse.at);
241        }
242        intervals.push(Interval { reg, class, range });
243    }
244    intervals.sort_by_key(|interval| (interval.range.start, interval.reg));
245
246    let mut assignment = Assignment::empty(func.vregs());
247    let mut active: Vec<Held> = Vec::new();
248    for interval in intervals {
249        active.retain(|held| held.range.end >= interval.range.start);
250        if forced.contains(&interval.reg) {
251            assignment.spill(interval.reg, interval.class);
252            continue;
253        }
254        assert!(
255            !env.order(interval.class).is_empty(),
256            "a value in a class the target hands out no registers from"
257        );
258        let two_address = reuses[index(interval.reg)]
259            .and_then(|reuse| coalesce(&assignment, &active, &blocked, interval, reuse));
260        let chosen = two_address.or_else(|| {
261            env.order(interval.class)
262                .iter()
263                .copied()
264                .find(|&at| available(&active, &blocked, interval, at, None))
265        });
266        match chosen {
267            Some(at) => {
268                assignment.places[index(interval.reg)] = Some(Place::Reg(at));
269                let reg = interval.reg;
270                active.push(Held { reg, class: interval.class, range: interval.range, at });
271            }
272            None => spill_one(&mut assignment, &mut active, &blocked, interval),
273        }
274    }
275    assignment
276}
277
278/// Whether a register is one this interval could have.
279///
280/// The exception is the value a reuse is coalescing with, which holds the register right up to the
281/// point the new value takes it over and is the one thing that may overlap.
282fn available(
283    active: &[Held],
284    blocked: &[Blocked],
285    interval: Interval,
286    at: PhysReg,
287    except: Option<Reg>,
288) -> bool {
289    let taken = active
290        .iter()
291        .any(|held| held.at == at && held.class == interval.class && Some(held.reg) != except);
292    let insisted = blocked.iter().any(|one| {
293        one.at == at && one.class == interval.class && one.range.overlaps(interval.range)
294    });
295    !taken && !insisted
296}
297
298/// The register the value being reused is in, when this instruction is the last thing that reads
299/// it and the register is otherwise free.
300fn coalesce(
301    assignment: &Assignment,
302    active: &[Held],
303    blocked: &[Blocked],
304    interval: Interval,
305    reuse: Reuse,
306) -> Option<PhysReg> {
307    let Some(Place::Reg(at)) = assignment.place(reuse.source) else { return None };
308    let source = active.iter().find(|held| held.reg == reuse.source)?;
309    // A value read again later needs its register after this instruction would have overwritten
310    // it, so the two really do have to be different and the rewrite really does have to copy.
311    let dies = source.range.end == reuse.at;
312    (dies && available(active, blocked, interval, at, Some(reuse.source))).then_some(at)
313}
314
315/// Sends one value to the stack: the one wanted for longest, since its register pays for itself
316/// over the most instructions.
317fn spill_one(
318    assignment: &mut Assignment,
319    active: &mut Vec<Held>,
320    blocked: &[Blocked],
321    interval: Interval,
322) {
323    // A value whose register the instructions in the way insist on for themselves is no use as a
324    // victim, because taking it over would put this value in a register it may not have.
325    let victim = active
326        .iter()
327        .enumerate()
328        .filter(|(_, held)| held.class == interval.class)
329        .filter(|(_, held)| available(&[], blocked, interval, held.at, None))
330        .max_by_key(|(_, held)| held.range.end)
331        .map(|(at, held)| (at, held.at, held.range.end));
332    match victim {
333        Some((victim, at, end)) if end > interval.range.end => {
334            let held = active.remove(victim);
335            assignment.spill(held.reg, held.class);
336            assignment.places[index(interval.reg)] = Some(Place::Reg(at));
337            let reg = interval.reg;
338            active.push(Held { reg, class: interval.class, range: interval.range, at });
339        }
340        _ => assignment.spill(interval.reg, interval.class),
341    }
342}
343
344/// The registers the instructions insist on, and where.
345///
346/// A physical register an operand names outright counts the same way. Nothing before allocation
347/// writes one except an instruction that has to, and it has to for the length of that one
348/// instruction, which is the same statement a fixed constraint makes.
349fn blocked(func: &Func, order: &Order) -> Vec<Blocked> {
350    let mut blocked = Vec::new();
351    for block in func.blocks() {
352        for inst in func.insts(block) {
353            let range = Range { start: order.early(inst), end: order.late(inst) };
354            for operand in &func[func[inst].operands] {
355                let at = match operand.constraint {
356                    Constraint::Fixed(at) => Some(at),
357                    _ => operand.reg.phys(),
358                };
359                if let Some(at) = at {
360                    blocked.push(Blocked { class: operand.class, at, range });
361                }
362            }
363        }
364    }
365    blocked
366}
367
368/// The values that have to be on the stack whatever else is true of them.
369fn forced(func: &Func) -> Vec<Reg> {
370    let mut forced = Vec::new();
371    for block in func.blocks() {
372        for inst in func.insts(block) {
373            for operand in &func[func[inst].operands] {
374                if operand.constraint == Constraint::Stack
375                    && operand.reg.is_virtual()
376                    && !forced.contains(&operand.reg)
377                {
378                    forced.push(operand.reg);
379                }
380            }
381        }
382    }
383    forced
384}
385
386/// The value each two address instruction reuses, by the virtual register it writes.
387fn reuses(func: &Func, order: &Order) -> Vec<Option<Reuse>> {
388    let mut reuses = vec![None; func.vregs()];
389    for block in func.blocks() {
390        for inst in func.insts(block) {
391            let operands = &func[func[inst].operands];
392            for operand in operands {
393                let Constraint::Reuse(other) = operand.constraint else { continue };
394                let number = operand.reg.number().and_then(|number| usize::try_from(number).ok());
395                let Some(number) = number else { continue };
396                let source = operands[usize::from(other)].reg;
397                reuses[number] = Some(Reuse { source, at: order.early(inst) });
398            }
399        }
400    }
401    reuses
402}
403
404/// A virtual register's number as a table index.
405fn index(reg: Reg) -> usize {
406    usize::try_from(reg.number().expect("a virtual register")).expect("a register number")
407}
408
409#[cfg(test)]
410mod tests {
411    use rucc_base::Interner;
412    use rucc_mir::{BlockCall, Opcode, Operand};
413    use rucc_target::x86_64::{GPR, R13, R14, R15, RAX, RCX, RDX, REGS, SYSV};
414
415    use super::*;
416
417    /// The x86-64 environment, with the last three of the allocation order held back as scratch.
418    fn env() -> Env {
419        let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
420        Env::new().with(GPR, order, scratch)
421    }
422
423    /// An environment with that many general purpose registers, for putting a function under
424    /// pressure without writing a hundred instructions.
425    fn narrow(count: usize) -> Env {
426        Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 1])
427    }
428
429    /// What a place is called, which is what an assertion reads.
430    fn named(place: Option<Place>) -> String {
431        match place {
432            Some(Place::Reg(reg)) => REGS.name(GPR, reg).expect("a register").to_string(),
433            Some(Place::Slot(slot)) => format!("slot {slot}"),
434            None => "nowhere".to_string(),
435        }
436    }
437
438    /// Where every value in a function went.
439    fn places(func: &Func, env: &Env) -> Vec<String> {
440        let order = Order::of(func);
441        let live = Live::of(func, &order);
442        let assignment = assign(func, &order, &live, env);
443        (0..func.vregs())
444            .map(|number| {
445                let reg = Reg::virtual_reg(u32::try_from(number).expect("a register number"));
446                named(assignment.place(reg))
447            })
448            .collect()
449    }
450
451    #[test]
452    fn two_values_that_are_never_both_wanted_share_a_register() {
453        let mut names = Interner::new();
454        let mut func = Func::new(names.intern("f"));
455        let opcode = Opcode::new(names.intern("x64.nop"));
456        let block = func.create_block();
457        let first = func.new_vreg(GPR);
458        let second = func.new_vreg(GPR);
459        func.build(block, opcode).def(first, GPR).finish();
460        func.build(block, opcode).uses(first, GPR).finish();
461        func.build(block, opcode).def(second, GPR).finish();
462        func.build(block, opcode).uses(second, GPR).finish();
463
464        // The first register in the order, twice, because the first value is finished with before
465        // the second one is written.
466        assert_eq!(places(&func, &env()), ["rax", "rax"]);
467    }
468
469    #[test]
470    fn two_values_that_are_both_wanted_do_not() {
471        let mut names = Interner::new();
472        let mut func = Func::new(names.intern("f"));
473        let opcode = Opcode::new(names.intern("x64.nop"));
474        let block = func.create_block();
475        let first = func.new_vreg(GPR);
476        let second = func.new_vreg(GPR);
477        func.build(block, opcode).def(first, GPR).finish();
478        func.build(block, opcode).def(second, GPR).finish();
479        func.build(block, opcode).uses(first, GPR).finish();
480        func.build(block, opcode).uses(second, GPR).finish();
481
482        assert_eq!(places(&func, &env()), ["rax", "rcx"]);
483    }
484
485    #[test]
486    fn the_value_wanted_longest_is_the_one_that_goes_to_the_stack() {
487        let mut names = Interner::new();
488        let mut func = Func::new(names.intern("f"));
489        let opcode = Opcode::new(names.intern("x64.nop"));
490        let block = func.create_block();
491        let long = func.new_vreg(GPR);
492        let short = func.new_vreg(GPR);
493        let third = func.new_vreg(GPR);
494        func.build(block, opcode).def(long, GPR).finish();
495        func.build(block, opcode).def(short, GPR).finish();
496        func.build(block, opcode).def(third, GPR).finish();
497        func.build(block, opcode).uses(short, GPR).finish();
498        func.build(block, opcode).uses(third, GPR).finish();
499        func.build(block, opcode).uses(long, GPR).finish();
500
501        // Two registers between three values. The one still wanted at the end of the function is
502        // the one whose register is worth the most to everybody else, so it is the one that goes.
503        assert_eq!(places(&func, &narrow(2)), ["slot 0", "rcx", "rax"]);
504    }
505
506    #[test]
507    fn a_register_an_instruction_insists_on_is_left_alone_over_that_instruction() {
508        let mut names = Interner::new();
509        let mut func = Func::new(names.intern("f"));
510        let opcode = Opcode::new(names.intern("x64.nop"));
511        let block = func.create_block();
512        let across = func.new_vreg(GPR);
513        let dividend = func.new_vreg(GPR);
514        let quotient = func.new_vreg(GPR);
515        let remainder = func.new_vreg(GPR);
516        func.build(block, opcode).def(across, GPR).finish();
517        func.build(block, opcode).def(dividend, GPR).finish();
518        func.build(block, opcode)
519            .operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
520            .operand(Operand::write_early(remainder, GPR).with(Constraint::Fixed(RDX)))
521            .operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
522            .finish();
523        func.build(block, opcode).uses(across, GPR).finish();
524
525        // Nothing is in `rax` or `rdx` anywhere near the division, including the three values the
526        // division itself names. They are moved in and out around it, which is a move the rewrite
527        // writes and not a decision taken here.
528        assert_eq!(places(&func, &env()), ["rcx", "rsi", "rsi", "rdi"]);
529    }
530
531    #[test]
532    fn a_value_an_instruction_can_only_read_from_memory_is_on_the_stack() {
533        let mut names = Interner::new();
534        let mut func = Func::new(names.intern("f"));
535        let opcode = Opcode::new(names.intern("x64.nop"));
536        let block = func.create_block();
537        let value = func.new_vreg(GPR);
538        func.build(block, opcode).def(value, GPR).finish();
539        func.build(block, opcode)
540            .operand(Operand::read(value, GPR).with(Constraint::Stack))
541            .finish();
542
543        assert_eq!(places(&func, &env()), ["slot 0"]);
544    }
545
546    #[test]
547    fn a_two_address_instruction_writes_the_register_it_read_when_it_can() {
548        let mut names = Interner::new();
549        let mut func = Func::new(names.intern("f"));
550        let opcode = Opcode::new(names.intern("x64.nop"));
551        let block = func.create_block();
552        let left = func.new_vreg(GPR);
553        let right = func.new_vreg(GPR);
554        let sum = func.new_vreg(GPR);
555        func.build(block, opcode).def(left, GPR).finish();
556        func.build(block, opcode).def(right, GPR).finish();
557        func.build(block, opcode)
558            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
559            .uses(left, GPR)
560            .uses(right, GPR)
561            .finish();
562        func.build(block, opcode).uses(right, GPR).finish();
563
564        // The addition reads the left value for the last time, so the answer goes where that was
565        // and the instruction is two address without a move in front of it.
566        assert_eq!(places(&func, &env()), ["rax", "rcx", "rax"]);
567    }
568
569    #[test]
570    fn a_two_address_instruction_that_cannot_gets_a_register_nothing_it_reads_is_in() {
571        let mut names = Interner::new();
572        let mut func = Func::new(names.intern("f"));
573        let opcode = Opcode::new(names.intern("x64.nop"));
574        let block = func.create_block();
575        let left = func.new_vreg(GPR);
576        let right = func.new_vreg(GPR);
577        let sum = func.new_vreg(GPR);
578        func.build(block, opcode).def(left, GPR).finish();
579        func.build(block, opcode).def(right, GPR).finish();
580        func.build(block, opcode)
581            .operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
582            .uses(left, GPR)
583            .uses(right, GPR)
584            .finish();
585        func.build(block, opcode).uses(left, GPR).finish();
586
587        // The left value is wanted afterwards, so the answer cannot have its register. It cannot
588        // have the right one's either, because the rewrite is about to write a move into it before
589        // the addition has read anything.
590        assert_eq!(places(&func, &env()), ["rax", "rcx", "rdx"]);
591    }
592
593    #[test]
594    fn a_value_live_across_a_whole_loop_holds_its_register_over_all_of_it() {
595        let mut names = Interner::new();
596        let mut func = Func::new(names.intern("f"));
597        let opcode = Opcode::new(names.intern("x64.nop"));
598        let head = func.create_block();
599        let body = func.create_block();
600        let carried = func.new_vreg(GPR);
601        let inside = func.new_vreg(GPR);
602        func.build(head, opcode).def(carried, GPR).finish();
603        *func.succs_mut(head) = vec![BlockCall::to(body)];
604        func.build(body, opcode).def(inside, GPR).finish();
605        func.build(body, opcode).uses(inside, GPR).uses(carried, GPR).finish();
606        *func.succs_mut(body) = vec![BlockCall::to(body)];
607
608        // The value inside the loop cannot have the carried one's register, even though nothing
609        // between the two definitions says so.
610        assert_eq!(places(&func, &env()), ["rax", "rcx"]);
611    }
612
613    #[test]
614    fn a_frame_says_what_each_of_its_slots_is_for() {
615        let mut names = Interner::new();
616        let mut func = Func::new(names.intern("f"));
617        let opcode = Opcode::new(names.intern("x64.nop"));
618        let block = func.create_block();
619        let first = func.new_vreg(GPR);
620        let second = func.new_vreg(GPR);
621        func.build(block, opcode).def(first, GPR).finish();
622        func.build(block, opcode).def(second, GPR).finish();
623        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
624
625        let order = Order::of(&func);
626        let live = Live::of(&func, &order);
627        let assignment = assign(&func, &order, &live, &narrow(1));
628        assert_eq!(assignment.spilled(), 1);
629        assert_eq!(assignment.slots(), [GPR]);
630        // A register that is already a register is where it is, and this has nothing to say about
631        // it.
632        assert_eq!(assignment.place(Reg::physical(RCX)), None);
633        assert_eq!(env().scratch(GPR), [R13, R14, R15]);
634    }
635}