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