Skip to main content

rucc_regalloc/
trace.rs

1//! Following every value from the instruction that wrote it to the instructions that read it.
2//!
3//! Design: `spec/optimizer/39-register-allocation.md` section 39.6, which asks for an independent
4//! verifier that every use reads the value its definition produced, and `spec/10-backend.md`
5//! section 10.4, which says a check like it runs in debug and CI builds.
6//!
7//! # Why there are two checkers
8//!
9//! [`crate::check`] reads the assignment. It asks whether the decision is one the machine can run:
10//! whether two values that are live at once were given the same place, whether anything is sitting
11//! in a register an instruction claims for itself, and so on. Its own module doc says what it
12//! leaves alone, which is the rewrite, on the grounds that the assignment is the decision and the
13//! rewrite is a transcription of it.
14//!
15//! This is the other half. A transcription can lose a value while the decision behind it was
16//! right, and the places it can lose one are exactly the places [`crate::rewrite`] does something:
17//! handing out scratch registers for a value that lives on the stack, moving a value into the
18//! register an instruction insists on, copying one operand into another for a two address form,
19//! and putting an edge's moves into an order that can be performed one at a time. Both #350 and
20//! #726 were bugs in that code and neither is a shape the assignment checker can see.
21//!
22//! # What it asks
23//!
24//! One question. At every instruction, does each register the instruction reads hold the value the
25//! operand said it wanted.
26//!
27//! It is asked only of the values the allocator placed. An operand that named a physical register
28//! before the rewrite named it for its own reasons, which are the calling convention, the frame, or
29//! the text of an `asm` statement the program wrote, and what is in that register is whatever those
30//! reasons put there. There is no value under it for a transcription to lose, so reading one is
31//! nothing this can be wrong about. Writing one is still followed, because a value the allocator
32//! did put in that register is gone once it happens.
33//!
34//! That is asked by walking the function with a note of which value is in each place, starting
35//! from nothing at the entry block. An instruction writing an operand puts that value in the place
36//! the operand ended up naming. A move puts what is in one place into another, or takes the note
37//! away when the place it reads from held nothing known. An edge carries the block's arguments
38//! into the block's parameters, and what arrives is checked and then goes on under the parameter's
39//! name, since from there on that is what the value is called.
40//!
41//! A block with two edges into it keeps only what both of them agree about, because a value that
42//! is in one place on one path and another place on the other is not in either. That is a
43//! fixed point and it is worked out first, before anything is reported, so that a loop is not
44//! complained about on the first time round before the back edge has been seen.
45//!
46//! # Why it is told the function twice
47//!
48//! [`shape`] is taken before the rewrite and holds what each operand's value was called and what
49//! each edge carried. The rewrite is what loses both: afterwards an operand names a physical
50//! register and an edge carries nothing. Checking a transcription means holding on to the thing
51//! that was transcribed, and a snapshot of the operand lists is a cheaper way to do that than a
52//! copy of the function.
53//!
54//! # Why it repeats work
55//!
56//! It works out where a value lives from the assignment again rather than reading it off the
57//! rewritten function, and it sequences nothing. A checker that shares its reasoning with the
58//! thing it checks agrees with it about the mistakes as well, and the bug it can never find is the
59//! one in the code they share.
60//!
61//! It is also allowed to be slow. The state is a map from places to values and it is copied at
62//! every edge, because a checker runs in debug builds and the thing it is checking is the thing
63//! that has to be fast.
64
65use std::collections::HashMap;
66use std::fmt;
67
68use rucc_mir::{Block, Func, Inst, Operand, Param, Reg, Role};
69use rucc_target::RegClass;
70
71use crate::assign::{Assignment, Place};
72use crate::rewrite::{At, Edit};
73
74/// One value read out of a place that was not holding it.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum Fault {
77    /// An instruction reads a place holding something other than the value its operand names.
78    Read {
79        /// The instruction doing the reading.
80        inst: Inst,
81        /// The place it reads.
82        place: Place,
83        /// Which file that place is in, since a register is a number inside its class.
84        class: RegClass,
85        /// The value the operand says is there.
86        wanted: Reg,
87        /// What is really there, if anything is known to be.
88        found: Option<Reg>,
89    },
90    /// An edge did not leave a block's parameter holding the argument the edge carried for it.
91    Arrived {
92        /// The block the edge leaves.
93        from: Block,
94        /// The block it goes to.
95        to: Block,
96        /// Where the parameter lives.
97        place: Place,
98        /// Which file that place is in.
99        class: RegClass,
100        /// The argument that was supposed to arrive there.
101        wanted: Reg,
102        /// What is really there, if anything is known to be.
103        found: Option<Reg>,
104    },
105}
106
107impl fmt::Display for Fault {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            Fault::Read { inst, place, class, wanted, found } => write!(
111                f,
112                "instruction {} reads {} out of {}, which {}",
113                inst.index(),
114                name(*wanted),
115                spelled(*class, *place),
116                holding(*found)
117            ),
118            Fault::Arrived { from, to, place, class, wanted, found } => write!(
119                f,
120                "the edge from block {} to block {} was to leave {} in {}, which {}",
121                from.index(),
122                to.index(),
123                name(*wanted),
124                spelled(*class, *place),
125                holding(*found)
126            ),
127        }
128    }
129}
130
131/// What the rewrite is about to lose: what each operand's value was called, and what each edge
132/// carries.
133///
134/// Taken with [`shape`], before [`crate::rewrite`] runs, and read by [`trace`] afterwards.
135#[derive(Debug, Clone, Default)]
136pub struct Shape {
137    /// Every instruction's operands as they were, by the instruction's index.
138    operands: Vec<Vec<Operand>>,
139    /// Every block's parameters, by the block's index.
140    params: Vec<Vec<Param>>,
141    /// Every block's edges out and what each one carries, by the block's index.
142    succs: Vec<Vec<Call>>,
143}
144
145/// One edge out of a block, as it was before the rewrite emptied it.
146#[derive(Debug, Clone)]
147struct Call {
148    block: Block,
149    args: Vec<Reg>,
150}
151
152/// What a function looked like before the rewrite.
153///
154/// # Panics
155///
156/// Panics on a function with more instructions than a `usize` counts, which is one no machine has
157/// the memory to hold.
158#[must_use]
159pub fn shape(func: &Func) -> Shape {
160    let mut shape = Shape {
161        operands: vec![Vec::new(); func.inst_count()],
162        params: vec![Vec::new(); func.block_count()],
163        succs: vec![Vec::new(); func.block_count()],
164    };
165    for block in func.blocks() {
166        shape.params[block.index()] = func[block].params.clone();
167        shape.succs[block.index()] = func[block]
168            .succs
169            .iter()
170            .map(|call| Call { block: call.block, args: call.args.clone() })
171            .collect();
172        for inst in func.insts(block) {
173            shape.operands[inst.index()] = func[func[inst].operands].to_vec();
174        }
175    }
176    shape
177}
178
179/// Everywhere the rewritten function reads a place that is not holding the value it wants.
180///
181/// An empty answer is the one every allocation is supposed to give. Anything else is a compiler
182/// bug rather than a program the compiler cannot handle, which is why [`crate::run`] asserts on it
183/// instead of reporting it as a diagnostic.
184///
185/// The function is the rewritten one, the shape is what [`shape`] took of it beforehand, and the
186/// edits are the ones the rewrite handed back. The three together are the whole of what the
187/// machine will be asked to run.
188#[must_use]
189pub fn trace(func: &Func, shape: &Shape, assignment: &Assignment, edits: &[Edit]) -> Vec<Fault> {
190    let filed = File::of(func, edits);
191    let mut entry: Vec<Option<State>> = vec![None; func.block_count()];
192    let Some(start) = func.entry() else { return Vec::new() };
193
194    // The entry block starts out knowing nothing, because nothing has run to put a value anywhere
195    // yet. A value the allocator placed and the function reads before writing is the assignment
196    // checker's question rather than this one's, and a register the function names itself is not
197    // read as a value at all.
198    entry[start.index()] = Some(State::new());
199    let mut queue = vec![start];
200    let mut ignored = Vec::new();
201    while let Some(block) = queue.pop() {
202        let Some(state) = entry[block.index()].clone() else { continue };
203        ignored.clear();
204        let out = body(func, shape, &filed, edits, block, state, &mut ignored);
205        let single = shape.succs[block.index()].len() == 1;
206        for call in &shape.succs[block.index()] {
207            let over =
208                cross(shape, &filed, edits, assignment, block, call, single, &out, &mut ignored);
209            if narrow(&mut entry[call.block.index()], &over) {
210                queue.push(call.block);
211            }
212        }
213    }
214
215    // Now that every block's state has settled, one pass to say what is wrong with it. Doing this
216    // inside the loop above would complain about the first time round a loop, before the back edge
217    // has had a chance to take anything away.
218    let mut faults = Vec::new();
219    for block in func.blocks() {
220        let Some(state) = entry[block.index()].clone() else { continue };
221        let out = body(func, shape, &filed, edits, block, state, &mut faults);
222        let single = shape.succs[block.index()].len() == 1;
223        for call in &shape.succs[block.index()] {
224            cross(shape, &filed, edits, assignment, block, call, single, &out, &mut faults);
225        }
226    }
227    faults
228}
229
230/// Everything wrong with a rewritten function, as an assertion message.
231#[must_use]
232pub fn report(faults: &[Fault]) -> String {
233    let places = if faults.len() == 1 { "place" } else { "places" };
234    let mut report = format!("the rewrite loses a value in {} {places}", faults.len());
235    for fault in faults {
236        report.push_str("\n  ");
237        report.push_str(&fault.to_string());
238    }
239    report
240}
241
242/// Which value is in which place, as far as anything is known.
243///
244/// A place with no entry is one nothing is known about, which is either a place nothing has
245/// written yet or one the paths into a block disagree about. Reading one is a fault, since the
246/// machine will read whatever is there.
247type State = HashMap<Spot, Reg>;
248
249/// A place and the file it is in.
250///
251/// The class is in here because a physical register is a number inside its class, so the first
252/// general purpose register and the first floating point register are both register zero and are
253/// not the same place at all.
254type Spot = (u8, Place);
255
256/// The place an operand of that class ended up naming.
257fn spot(class: RegClass, place: Place) -> Spot {
258    (class.number(), place)
259}
260
261/// Which edits go where, as indexes into the list the rewrite handed back.
262///
263/// The rewrite hands its edits back in the order it made them, which is every instruction's and
264/// then every edge's, so a walk in program order has to be able to ask for the ones at a point
265/// rather than read them in the order they arrived.
266#[derive(Debug, Default)]
267struct File {
268    before: Vec<Vec<usize>>,
269    after: Vec<Vec<usize>>,
270    start_of: Vec<Vec<usize>>,
271    end_of: Vec<Vec<usize>>,
272}
273
274impl File {
275    /// The edits of a function, filed by where in it they go.
276    fn of(func: &Func, edits: &[Edit]) -> Self {
277        let mut filed = File {
278            before: vec![Vec::new(); func.inst_count()],
279            after: vec![Vec::new(); func.inst_count()],
280            start_of: vec![Vec::new(); func.block_count()],
281            end_of: vec![Vec::new(); func.block_count()],
282        };
283        for (index, edit) in edits.iter().enumerate() {
284            match edit.at {
285                At::Before(inst) => filed.before[inst.index()].push(index),
286                At::After(inst) => filed.after[inst.index()].push(index),
287                At::StartOf(block) => filed.start_of[block.index()].push(index),
288                At::EndOf(block) => filed.end_of[block.index()].push(index),
289            }
290        }
291        filed
292    }
293}
294
295/// Walks one block, saying what is where at the end of it and what went wrong on the way.
296///
297/// The edits an edge into this block turned into are not applied here. They belong to the edge and
298/// [`cross`] has already made them true in the state this is handed.
299fn body(
300    func: &Func,
301    shape: &Shape,
302    filed: &File,
303    edits: &[Edit],
304    block: Block,
305    mut state: State,
306    faults: &mut Vec<Fault>,
307) -> State {
308    for inst in func.insts(block) {
309        for &edit in &filed.before[inst.index()] {
310            moved(&mut state, &edits[edit]);
311        }
312        let was = &shape.operands[inst.index()];
313        let now = &func[func[inst].operands];
314
315        // Every read first and every write afterwards, because an instruction reads its operands
316        // before it writes its answer. A write that lands on something the same instruction still
317        // wants to read is the assignment checker's question and it has already asked it.
318        //
319        // A read of a register the function named itself is skipped, because it is not reading a
320        // value the allocator placed. It is reading whatever the machine has in that register, put
321        // there by the calling convention, by the frame, or by the program's own `asm` text, and
322        // none of that is something a transcription can lose. A write of one is still recorded,
323        // since a value the allocator did place into that register is gone once it happens and a
324        // later read of it should say so.
325        for (operand, place) in was.iter().zip(now.iter()) {
326            let Some(at) = landed(place) else { continue };
327            if operand.role != Role::Use {
328                continue;
329            }
330            if operand.reg.phys().is_some() {
331                continue;
332            }
333            let found = state.get(&spot(operand.class, at)).copied();
334            if found != Some(operand.reg) {
335                let (class, wanted) = (operand.class, operand.reg);
336                faults.push(Fault::Read { inst, place: at, class, wanted, found });
337            }
338        }
339        for (operand, place) in was.iter().zip(now.iter()) {
340            let Some(at) = landed(place) else { continue };
341            if !operand.role.is_def() {
342                continue;
343            }
344            state.insert(spot(operand.class, at), operand.reg);
345        }
346        for &edit in &filed.after[inst.index()] {
347            moved(&mut state, &edits[edit]);
348        }
349    }
350    state
351}
352
353/// Carries one edge's values into the block it goes to, and says what is where when they arrive.
354///
355/// The moves go at the end of the block the edge leaves when that is its only edge out, and at the
356/// start of the block it goes to otherwise, which is safe exactly because a block reached by an
357/// edge from a block with two of them has no other edge into it.
358#[allow(clippy::too_many_arguments, reason = "an edge is the two blocks and everything between")]
359fn cross(
360    shape: &Shape,
361    filed: &File,
362    edits: &[Edit],
363    assignment: &Assignment,
364    from: Block,
365    call: &Call,
366    single: bool,
367    out: &State,
368    faults: &mut Vec<Fault>,
369) -> State {
370    let mut state = out.clone();
371    let params = &shape.params[call.block.index()];
372    let list =
373        if single { &filed.end_of[from.index()] } else { &filed.start_of[call.block.index()] };
374    for &edit in list {
375        moved(&mut state, &edits[edit]);
376    }
377
378    // What each parameter is called from here on. Worked out from the state the moves left and
379    // then written back all at once, because two parameters can be in each other's places and
380    // renaming one before the other has been read would lose the second.
381    let mut arrived = Vec::new();
382    for (param, &arg) in params.iter().zip(&call.args) {
383        let Some(at) = home(assignment, param.reg) else { continue };
384        let found = state.get(&spot(param.class, at)).copied();
385        if found != Some(arg) {
386            let (to, class) = (call.block, param.class);
387            faults.push(Fault::Arrived { from, to, place: at, class, wanted: arg, found });
388        }
389        arrived.push((spot(param.class, at), param.reg));
390    }
391    for (spot, reg) in arrived {
392        state.insert(spot, reg);
393    }
394    state
395}
396
397/// Performs one move on the state, which is putting what is in one place into another.
398///
399/// A move out of a place nothing is known about takes the note away rather than leaving a stale
400/// one, since what it copied is whatever was there.
401fn moved(state: &mut State, edit: &Edit) {
402    let to = spot(edit.class, edit.mov.to);
403    let from = spot(edit.class, edit.mov.from);
404    match state.get(&from).copied() {
405        Some(reg) => state.insert(to, reg),
406        None => state.remove(&to),
407    };
408}
409
410/// Keeps only what a block's state and an edge into it agree about, and says whether that changed
411/// anything.
412///
413/// A block nothing has reached yet takes the edge's state whole. After that every edge can only
414/// take something away, which is what makes the walk finish.
415fn narrow(entry: &mut Option<State>, over: &State) -> bool {
416    match entry {
417        None => {
418            *entry = Some(over.clone());
419            true
420        }
421        Some(state) => {
422            let before = state.len();
423            state.retain(|spot, reg| over.get(spot) == Some(&*reg));
424            state.len() != before
425        }
426    }
427}
428
429/// Where an operand ended up, which after the rewrite is always a physical register.
430fn landed(operand: &Operand) -> Option<Place> {
431    operand.reg.phys().map(Place::Reg)
432}
433
434/// Where a value lives, whether the assignment put it there or it was a physical register already.
435///
436/// Nothing for a value that is neither, which is a value the assignment was never asked about. The
437/// assignment checker reports that as a value with nowhere to live, so this leaves it alone rather
438/// than saying the same thing twice.
439fn home(assignment: &Assignment, reg: Reg) -> Option<Place> {
440    assignment.place(reg).or_else(|| reg.phys().map(Place::Reg))
441}
442
443/// What a value is called in a report.
444fn name(reg: Reg) -> String {
445    match reg.number() {
446        Some(number) => format!("%{number}"),
447        None => match reg.phys() {
448            Some(at) => format!("register {}", at.number()),
449            None => "nothing".to_owned(),
450        },
451    }
452}
453
454/// What a place is called in a report, without the target's name for it, since this crate holds
455/// nothing of any target.
456fn spelled(class: RegClass, place: Place) -> String {
457    match place {
458        Place::Reg(at) => format!("register {} of class {}", at.number(), class.number()),
459        Place::Slot(slot) => format!("slot {slot}"),
460    }
461}
462
463/// What a place is holding, as the end of a sentence.
464fn holding(found: Option<Reg>) -> String {
465    match found {
466        Some(reg) => format!("holds {}", name(reg)),
467        None => "holds nothing anything has put there".to_owned(),
468    }
469}
470
471#[cfg(test)]
472mod tests {
473    use rucc_base::Interner;
474    use rucc_mir::{BlockCall, Constraint, Opcode, Operand};
475    use rucc_target::x86_64::{GPR, RAX, RSP, SYSV};
476
477    use super::*;
478    use crate::assign::{Env, assign};
479    use crate::live::Live;
480    use crate::moves::Move;
481    use crate::order::Order;
482    use crate::rewrite::rewrite;
483
484    /// The x86-64 environment, with the last three of the allocation order held back as scratch.
485    fn env() -> Env {
486        let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
487        Env::new().with(GPR, order, scratch)
488    }
489
490    /// An environment with that many general purpose registers and two scratch after them.
491    fn narrow(count: usize) -> Env {
492        Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 2])
493    }
494
495    /// Allocates a function and hands back everything the checker is told about it.
496    fn allocate(func: &mut Func, env: &Env) -> (Shape, Assignment, Vec<Edit>) {
497        let order = Order::of(func);
498        let live = Live::of(func, &order);
499        let mut assignment = assign(func, &order, &live, env);
500        let taken = shape(func);
501        let edits = rewrite(func, &mut assignment, env);
502        (taken, assignment, edits)
503    }
504
505    /// What the checker says about a rewritten function, as lines an assertion can read.
506    fn said(func: &Func, taken: &Shape, assignment: &Assignment, edits: &[Edit]) -> Vec<String> {
507        trace(func, taken, assignment, edits).iter().map(ToString::to_string).collect()
508    }
509
510    #[test]
511    fn every_value_an_instruction_reads_is_the_one_that_was_written_where_it_reads_it() {
512        let mut names = Interner::new();
513        let mut func = Func::new(names.intern("f"));
514        let opcode = Opcode::new(names.intern("x64.nop"));
515        let block = func.create_block();
516        let first = func.new_vreg(GPR);
517        let second = func.new_vreg(GPR);
518        func.build(block, opcode).def(first, GPR).finish();
519        func.build(block, opcode).def(second, GPR).finish();
520        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
521
522        let (taken, assignment, edits) = allocate(&mut func, &env());
523        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
524    }
525
526    #[test]
527    fn a_value_that_lives_on_the_stack_is_followed_through_the_slot_it_lives_in() {
528        let mut names = Interner::new();
529        let mut func = Func::new(names.intern("f"));
530        let opcode = Opcode::new(names.intern("x64.nop"));
531        let block = func.create_block();
532        let first = func.new_vreg(GPR);
533        let second = func.new_vreg(GPR);
534        let third = func.new_vreg(GPR);
535        func.build(block, opcode).def(first, GPR).finish();
536        func.build(block, opcode).def(second, GPR).finish();
537        func.build(block, opcode).def(third, GPR).finish();
538        func.build(block, opcode).uses(first, GPR).uses(second, GPR).uses(third, GPR).finish();
539
540        // Two registers and three values wanted at once, so one of them is stored to a slot and
541        // read back out of it, and the checker has to follow it both ways to say nothing is wrong.
542        let (taken, assignment, edits) = allocate(&mut func, &narrow(2));
543        assert_eq!(assignment.spilled(), 1);
544        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
545    }
546
547    #[test]
548    fn an_operand_the_rewrite_pointed_at_the_wrong_register_is_reported() {
549        let mut names = Interner::new();
550        let mut func = Func::new(names.intern("f"));
551        let opcode = Opcode::new(names.intern("x64.nop"));
552        let block = func.create_block();
553        let first = func.new_vreg(GPR);
554        let second = func.new_vreg(GPR);
555        let read = {
556            func.build(block, opcode).def(first, GPR).finish();
557            func.build(block, opcode).def(second, GPR).finish();
558            func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish()
559        };
560
561        let (taken, assignment, edits) = allocate(&mut func, &env());
562        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
563
564        // The checker has to be checking, so point one operand at the register next door, which is
565        // what a rewrite that handed out the wrong register would leave, and see it named. The
566        // assignment behind it is untouched and is still the right one.
567        let list = func[read].operands;
568        func[list][0].reg = func[list][1].reg;
569
570        assert_eq!(
571            said(&func, &taken, &assignment, &edits),
572            ["instruction 2 reads %0 out of register 1 of class 0, which holds %1"]
573        );
574    }
575
576    #[test]
577    fn a_move_that_writes_the_wrong_register_is_an_instruction_reading_the_wrong_value() {
578        let mut names = Interner::new();
579        let mut func = Func::new(names.intern("f"));
580        let opcode = Opcode::new(names.intern("x64.nop"));
581        let block = func.create_block();
582        let dividend = func.new_vreg(GPR);
583        let quotient = func.new_vreg(GPR);
584        func.build(block, opcode).def(dividend, GPR).finish();
585        func.build(block, opcode)
586            .operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
587            .operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
588            .finish();
589        func.build(block, opcode).uses(quotient, GPR).finish();
590        func.build(block, opcode).uses(dividend, GPR).finish();
591
592        let (taken, assignment, mut edits) = allocate(&mut func, &env());
593        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
594
595        // The move that carries the dividend into the register the instruction insists on, sent
596        // to the register next door instead. That is the shape of a rewrite that hands out the
597        // wrong scratch register, and it has to be caught at the instruction that reads it.
598        edits[0].mov.to = Place::Reg(SYSV.int_order[2]);
599        assert_eq!(
600            said(&func, &taken, &assignment, &edits),
601            ["instruction 1 reads %0 out of register 0 of class 0, which holds nothing anything \
602              has put there"]
603        );
604    }
605
606    #[test]
607    fn a_value_carried_over_an_edge_goes_on_under_the_name_the_block_it_arrives_in_gives_it() {
608        let mut names = Interner::new();
609        let mut func = Func::new(names.intern("f"));
610        let opcode = Opcode::new(names.intern("x64.nop"));
611        let head = func.create_block();
612        let tail = func.create_block();
613        let value = func.new_vreg(GPR);
614        func.build(head, opcode).def(value, GPR).finish();
615        let arrived = func.append_param(tail, GPR);
616        *func.succs_mut(head) = vec![BlockCall::with(tail, vec![value])];
617        func.build(tail, opcode).uses(arrived, GPR).finish();
618
619        let (taken, assignment, edits) = allocate(&mut func, &env());
620        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
621    }
622
623    #[test]
624    fn two_values_that_swap_on_an_edge_arrive_the_right_way_round_only_in_the_order_they_were_put_in()
625     {
626        let mut names = Interner::new();
627        let mut func = Func::new(names.intern("f"));
628        let opcode = Opcode::new(names.intern("x64.nop"));
629        let head = func.create_block();
630        let body = func.create_block();
631        let first = func.new_vreg(GPR);
632        let second = func.new_vreg(GPR);
633        func.build(head, opcode).def(first, GPR).finish();
634        func.build(head, opcode).def(second, GPR).finish();
635        let left = func.append_param(body, GPR);
636        let right = func.append_param(body, GPR);
637        *func.succs_mut(head) = vec![BlockCall::with(body, vec![first, second])];
638        func.build(body, opcode).uses(left, GPR).uses(right, GPR).finish();
639        *func.succs_mut(body) = vec![BlockCall::with(body, vec![right, left])];
640
641        let (taken, assignment, mut edits) = allocate(&mut func, &env());
642        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
643
644        // The same swap written as the two moves it looks like, which is what a sequencer that
645        // did not notice the cycle would leave. The second move reads a register the first has
646        // already written, so both parameters end up holding the same value.
647        let (to, from) = (Place::Reg(SYSV.int_order[0]), Place::Reg(SYSV.int_order[1]));
648        edits.truncate(edits.len() - 3);
649        edits.push(Edit { at: At::EndOf(body), mov: Move::new(to, from), class: GPR });
650        edits.push(Edit { at: At::EndOf(body), mov: Move::new(from, to), class: GPR });
651
652        assert_eq!(
653            said(&func, &taken, &assignment, &edits),
654            ["the edge from block 1 to block 1 was to leave %2 in register 1 of class 0, which \
655                 holds %3"]
656        );
657    }
658
659    #[test]
660    fn a_loop_is_walked_until_it_settles_rather_than_reported_the_first_time_round() {
661        let mut names = Interner::new();
662        let mut func = Func::new(names.intern("f"));
663        let opcode = Opcode::new(names.intern("x64.nop"));
664        let head = func.create_block();
665        let body = func.create_block();
666        let latch = func.create_block();
667        let out = func.create_block();
668        let start = func.new_vreg(GPR);
669        func.build(head, opcode).def(start, GPR).finish();
670        let counter = func.append_param(body, GPR);
671        *func.succs_mut(head) = vec![BlockCall::with(body, vec![start])];
672        let next = func.new_vreg(GPR);
673        func.build(body, opcode).def(next, GPR).uses(counter, GPR).finish();
674        *func.succs_mut(body) = vec![BlockCall::to(latch), BlockCall::to(out)];
675        func.build(latch, opcode).finish();
676        *func.succs_mut(latch) = vec![BlockCall::with(body, vec![next])];
677        func.build(out, opcode).finish();
678
679        // The back edge is what the counter arrives on the second time round, and the checker only
680        // reports anything once every edge into the block has been taken into account.
681        let (taken, assignment, edits) = allocate(&mut func, &env());
682        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
683    }
684
685    #[test]
686    fn a_value_the_two_ways_into_a_block_leave_in_different_places_is_not_one_it_may_read() {
687        let mut names = Interner::new();
688        let mut func = Func::new(names.intern("f"));
689        let opcode = Opcode::new(names.intern("x64.nop"));
690        let entry = func.create_block();
691        let arm = func.create_block();
692        let tail = func.create_block();
693        let value = func.new_vreg(GPR);
694        func.build(entry, opcode).def(value, GPR).finish();
695        *func.succs_mut(entry) = vec![BlockCall::to(arm), BlockCall::to(tail)];
696        func.build(arm, opcode).finish();
697        *func.succs_mut(arm) = vec![BlockCall::to(tail)];
698        func.build(tail, opcode).uses(value, GPR).finish();
699
700        let (taken, assignment, mut edits) = allocate(&mut func, &env());
701        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
702
703        // One arm of the diamond writes over the register the value is in. The block the two arms
704        // meet in reads it, and what it gets depends on which way the program came, which is
705        // exactly the case a walk that only followed one path would miss.
706        let at = Place::Reg(SYSV.int_order[0]);
707        let elsewhere = Place::Reg(SYSV.int_order[1]);
708        edits.push(Edit { at: At::EndOf(arm), mov: Move::new(at, elsewhere), class: GPR });
709
710        assert_eq!(
711            said(&func, &taken, &assignment, &edits),
712            ["instruction 2 reads %0 out of register 0 of class 0, which holds nothing anything \
713              has put there"]
714        );
715    }
716
717    #[test]
718    fn a_register_the_function_names_itself_is_one_it_may_read_without_writing_it_first() {
719        let mut names = Interner::new();
720        let mut func = Func::new(names.intern("f"));
721        let opcode = Opcode::new(names.intern("x64.nop"));
722        let block = func.create_block();
723        func.build(block, opcode).operand(Operand::read(Reg::physical(RSP), GPR)).finish();
724
725        // The stack pointer is not the allocator's to hand out and nothing in the function writes
726        // it, so a checker that asked the same question of it as of a value would read every frame
727        // reference as a read of a register nobody had written.
728        let (taken, assignment, edits) = allocate(&mut func, &env());
729        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
730    }
731
732    #[test]
733    fn a_register_the_function_names_itself_is_readable_after_a_value_has_been_put_in_it() {
734        let mut names = Interner::new();
735        let mut func = Func::new(names.intern("f"));
736        let opcode = Opcode::new(names.intern("x64.nop"));
737        let block = func.create_block();
738        let argument = func.new_vreg(GPR);
739        func.build(block, opcode)
740            .operand(Operand::write(argument, GPR).with(Constraint::Fixed(SYSV.int_order[0])))
741            .finish();
742        func.build(block, opcode)
743            .operand(Operand::read(Reg::physical(SYSV.int_order[0]), GPR))
744            .finish();
745
746        // This is a function whose argument arrives in a register and whose body is an `asm`
747        // statement naming that same register in its own text. The argument is the allocator's
748        // value and the register is the program's name for the machine, and the two meeting in
749        // one place is the ordinary way an `asm` statement reads what it was passed rather than
750        // anything having gone wrong.
751        let (taken, assignment, edits) = allocate(&mut func, &env());
752        assert_eq!(said(&func, &taken, &assignment, &edits), Vec::<String>::new());
753    }
754
755    #[test]
756    fn what_is_wrong_is_reported_in_a_sentence_that_says_how_many_things_are_wrong() {
757        let fault = Fault::Read {
758            inst: Inst::new(3),
759            place: Place::Slot(1),
760            class: GPR,
761            wanted: Reg::virtual_reg(2),
762            found: None,
763        };
764        assert_eq!(
765            report(&[fault]),
766            "the rewrite loses a value in 1 place\n  instruction 3 reads %2 out of slot 1, which \
767             holds nothing anything has put there"
768        );
769    }
770}