Skip to main content

rucc_codegen/
changes.rs

1//! Proposing a set of machine level changes, asking whether the target takes them, and either
2//! committing the set or dropping it.
3//!
4//! Design: `spec/optimizer/37-machine-level-optimization.md` sections 37.2 and 37.3.
5//!
6//! Section 37.3 quotes `gcc/combine.cc` on what a machine level rewrite is: substitute the earlier
7//! instruction into the later one, ask the machine description whether the result is an instruction
8//! this target has, install it if it is and put everything back if it is not. Section 37.2 reads
9//! `gcc/rtl-ssa/changes.cc` and says the same thing about the arrangement rather than the rewrite,
10//! which is that the propose, validate and commit belongs to one named component rather than to
11//! each pass in its own words. This is that component.
12//!
13//! # Nothing is written until the whole set is taken
14//!
15//! A proposal holds what the instruction would become by value: the operands in a vector of their
16//! own, the addressing mode as an [`mir::Amode`] rather than as a reference into the function's
17//! arena, the immediate as a number. So abandoning a set is dropping it and there is no undo to
18//! get wrong. That is the difference between this and GCC's, which edits the RTL in place and
19//! keeps a list of what to put back, and it is available here because machine IR keeps an
20//! instruction's parts in arenas the proposal can stay out of until the last moment.
21//!
22//! # Why a set rather than an instruction
23//!
24//! Because the interesting rewrites are all of them or none. Folding an address into the three
25//! instructions that read it is worth doing when the address computation goes, and folding it into
26//! two of the three is worth nothing: the computation stays for the third, the address is worked
27//! out twice, and the registers it reads are live across all three. `crate::fold` had that rule
28//! written into it by hand and so would every pass after it.
29//!
30//! # What the target is asked
31//!
32//! [`MachineInsts`] is the whole of it, and every question in it is answered out of the same
33//! description the allocator and the encoder read. An opcode this machine does not have, an
34//! operand vector that is not the shape the opcode's form says, an immediate on an instruction
35//! that carries none, an addressing mode on one that has none, a scale this machine cannot write:
36//! each of those is a refusal, and a refusal is the whole set's.
37//!
38//! What is checked beyond the target's description is the part that is about the function rather
39//! than about the machine. An instruction may be named once in a set, it has to still be in the
40//! function, and taking an instruction out is refused while anything still reads what it wrote.
41//! That last one is what the set is for, so [`Changes`] is the thing that knows it rather than
42//! each pass.
43//!
44//! # What the read counts are worth after allocation
45//!
46//! Less, and they are still true. A count is how many operands in the function name a register,
47//! and while machine IR is in SSA form that is the whole answer to whether anything reads what an
48//! instruction wrote, because the register is written once. Once the allocator has run it is not:
49//! `%rax` is written all over the function and a count of the reads of it is a count of the reads
50//! of every one of those writes together.
51//!
52//! What that costs is optimizations rather than correctness. A count of zero still means nothing
53//! anywhere reads the register, so a removal the framework takes is a removal nothing was reading;
54//! what it will not take is the many where the register is read further down about a different
55//! write. So a pass that runs after allocation and removes instructions has to have its own reason,
56//! which is why [`crate::copies`] has one and says what it is, and a pass that rewrites rather than
57//! removes has the whole of the framework as usual.
58//!
59//! # Reading a register somewhere else
60//!
61//! A pass that takes an instruction out has to send whatever read it somewhere, and what that is
62//! is one register in place of another in an instruction that is otherwise the instruction it
63//! already was. That is [`Changes::rename`], and it is a proposal of its own rather than a plan
64//! with one operand changed, because the shape is what the description has something to say about
65//! and a rename changes no shape. An instruction this machine has with one register in an operand
66//! is one it has with another of the same class, so the class is the whole of what is checked.
67//!
68//! It is also the only way to say it about the instructions whose operand vector the description
69//! does not name, which on this machine is a call. How many registers a call passes is a fact about
70//! the signature rather than about the instruction, so `crates/rucc-target/src/x86_64/insts.rs`
71//! writes nothing down for it and a plan for one would be turned down for a shape nobody ever
72//! claimed.
73//!
74//! # The arguments an edge carries
75//!
76//! Those are reads too, and they are in no operand vector. A block's parameters are where the
77//! values a block is reached with arrive, the arguments on the edge are where they come from, and
78//! a pass sending every reader of a register somewhere else has these to send as well.
79//! [`Changes::carry`] is that, and like a plan it is by value: what the edge would carry rather
80//! than what to do to what it carries.
81
82use std::collections::HashMap;
83
84use rucc_base::{Interner, Symbol};
85use rucc_mir::{self as mir, Role};
86use rucc_target::MachineInsts;
87
88/// What an instruction would become.
89///
90/// Every part is held by value rather than as a reference into the function, which is what lets a
91/// proposal be dropped rather than undone. [`Changes::commit`] is what puts the parts in the
92/// arenas, and until it runs the function does not know this exists.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct Plan {
95    /// Which instruction it becomes.
96    pub opcode: mir::Opcode,
97    /// Its operands, the ones it writes before the ones it reads, with the registers its
98    /// addressing mode names last. The order is the one [`mir::InstBuilder`] keeps and the
99    /// printer, the parser and the allocator all read.
100    pub operands: Vec<mir::Operand>,
101    /// Its immediate, if the instruction carries one.
102    pub imm: Option<i64>,
103    /// Its addressing mode, if the instruction has one. The base and the index are positions in
104    /// `operands`.
105    pub amode: Option<mir::Amode>,
106    /// The symbol it names, which is the callee of a direct call.
107    pub symbol: Option<Symbol>,
108}
109
110impl Plan {
111    /// The instruction as it stands, which is where a rewrite starts from.
112    #[must_use]
113    pub fn of(func: &mir::Func, inst: mir::Inst) -> Self {
114        let data = &func[inst];
115        Self {
116            opcode: data.opcode,
117            operands: func[data.operands].to_vec(),
118            imm: data.imm.map(|at| func[at].0),
119            amode: data.mem.map(|at| func[at]),
120            symbol: data.symbol,
121        }
122    }
123
124    /// The registers it reads, which is what a removal has to count.
125    fn reads(&self) -> impl Iterator<Item = mir::Reg> + use<'_> {
126        self.operands.iter().filter(|operand| operand.role == Role::Use).map(|operand| operand.reg)
127    }
128}
129
130/// Why the target or the function would not have a set.
131///
132/// One instruction's refusal rather than the set's, because a pass that wants to know what it did
133/// wrong wants to know where, and because the tests below are clearer for it. Every one of them
134/// turns down the set it is in.
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum Refusal {
137    /// The set names that instruction twice, so what it becomes depends on which half wins.
138    Twice(mir::Inst),
139    /// That instruction is not in the function, which is a set built against a function something
140    /// else has changed since.
141    Gone(mir::Inst),
142    /// This target has no instruction of that name.
143    Unknown(mir::Inst),
144    /// The operand vector is not the shape the opcode's description says it is.
145    Operands(mir::Inst),
146    /// An immediate on an instruction that carries none, or none on one that does.
147    Imm(mir::Inst),
148    /// An addressing mode on an instruction that has none, or none on one that does.
149    Mem(mir::Inst),
150    /// An index multiplied by something this machine cannot write.
151    Scale(mir::Inst),
152    /// Taking that instruction out would leave something reading a register it wrote.
153    Read(mir::Inst),
154    /// A rename would put a register of one class where the instruction reads another.
155    Class(mir::Inst),
156    /// The set says twice what one edge carries, or the block it leaves has no such edge.
157    Edge(mir::Block, usize),
158    /// What the set would have an edge carry is not as many values as the block it goes to takes.
159    Args(mir::Block, usize),
160}
161
162/// How many times each register is read, kept across the commits of one pass.
163///
164/// A removal has to know whether anything still reads what the instruction wrote, and asking the
165/// function that question once per commit is the length of the function once per commit. So it is
166/// asked once and the answer is carried, which each commit brings up to date with what it did.
167#[derive(Debug, Clone, Default)]
168pub struct Reads {
169    counts: HashMap<mir::Reg, usize>,
170}
171
172impl Reads {
173    /// Every read in the function, counting the arguments an edge carries as reads, which they
174    /// are.
175    #[must_use]
176    pub fn of(func: &mir::Func) -> Self {
177        let mut counts: HashMap<mir::Reg, usize> = HashMap::new();
178        for block in func.blocks() {
179            for inst in func.insts(block) {
180                for operand in &func[func[inst].operands] {
181                    if operand.role == Role::Use {
182                        *counts.entry(operand.reg).or_insert(0) += 1;
183                    }
184                }
185            }
186            for call in &func[block].succs {
187                for &arg in &call.args {
188                    *counts.entry(arg).or_insert(0) += 1;
189                }
190            }
191        }
192        Self { counts }
193    }
194
195    /// How many reads of that register there are.
196    #[must_use]
197    pub fn count(&self, reg: mir::Reg) -> usize {
198        self.counts.get(&reg).copied().unwrap_or(0)
199    }
200
201    /// Records one read more.
202    fn gained(&mut self, reg: mir::Reg) {
203        *self.counts.entry(reg).or_insert(0) += 1;
204    }
205
206    /// Records one read fewer.
207    fn lost(&mut self, reg: mir::Reg) {
208        if let Some(count) = self.counts.get_mut(&reg) {
209            *count = count.saturating_sub(1);
210        }
211    }
212}
213
214/// What one instruction in a set would have done to it.
215#[derive(Debug, Clone, PartialEq, Eq)]
216enum What {
217    /// Becomes that.
218    Rewrite(Plan),
219    /// Reads the second register wherever it reads the first, and is otherwise the instruction it
220    /// already is.
221    Rename { from: mir::Reg, into: mir::Reg },
222    /// Goes.
223    Remove,
224}
225
226/// What a set would have one edge carry.
227#[derive(Debug, Clone, PartialEq, Eq)]
228struct Carried {
229    /// The block the edge leaves.
230    from: mir::Block,
231    /// Which of that block's edges, in the order the block holds them.
232    at: usize,
233    /// The registers it would carry, one per parameter of the block it goes to.
234    args: Vec<mir::Reg>,
235}
236
237/// A set of changes to one function, proposed together and taken together.
238///
239/// The order proposals are added in is the order they are applied in, which matters only for the
240/// reading of a commit that both rewrites and removes: nothing here depends on it, since a plan
241/// says what an instruction becomes rather than what to do to what it is.
242#[derive(Debug, Clone, Default)]
243pub struct Changes {
244    changes: Vec<(mir::Inst, What)>,
245    carries: Vec<Carried>,
246}
247
248impl Changes {
249    /// A set with nothing in it.
250    #[must_use]
251    pub fn new() -> Self {
252        Self::default()
253    }
254
255    /// How many changes the set is about, counting the edges along with the instructions.
256    #[must_use]
257    pub fn len(&self) -> usize {
258        self.changes.len() + self.carries.len()
259    }
260
261    /// Whether the set is about nothing, which commits and changes nothing.
262    #[must_use]
263    pub fn is_empty(&self) -> bool {
264        self.changes.is_empty() && self.carries.is_empty()
265    }
266
267    /// Proposes that the instruction become that.
268    pub fn rewrite(&mut self, inst: mir::Inst, plan: Plan) {
269        self.changes.push((inst, What::Rewrite(plan)));
270    }
271
272    /// Proposes that the instruction read the second register wherever it reads the first.
273    ///
274    /// Only where it reads it. What an instruction writes is what the rest of the function knows it
275    /// by, and changing that is a different proposal from this one.
276    pub fn rename(&mut self, inst: mir::Inst, from: mir::Reg, into: mir::Reg) {
277        self.changes.push((inst, What::Rename { from, into }));
278    }
279
280    /// Proposes that the edge leaving that block carry those registers.
281    ///
282    /// Which edge is its position in the block's own list of them, which is what
283    /// [`mir::Func::succs_mut`] hands back and what the terminator's conditions are written against.
284    pub fn carry(&mut self, from: mir::Block, at: usize, args: Vec<mir::Reg>) {
285        self.carries.push(Carried { from, at, args });
286    }
287
288    /// Proposes that the instruction go.
289    pub fn remove(&mut self, inst: mir::Inst) {
290        self.changes.push((inst, What::Remove));
291    }
292
293    /// Why the set would not be taken, or `None` if it would.
294    ///
295    /// Asked of the function as it stands and of the target's description of itself. Nothing here
296    /// changes anything, so a pass may ask, decide the answer is not worth having, and drop the
297    /// set.
298    #[must_use]
299    pub fn refused(
300        &self,
301        func: &mir::Func,
302        reads: &Reads,
303        names: &Interner,
304        machine: &MachineInsts,
305    ) -> Option<Refusal> {
306        for (at, &(inst, _)) in self.changes.iter().enumerate() {
307            if self.changes[..at].iter().any(|&(other, _)| other == inst) {
308                return Some(Refusal::Twice(inst));
309            }
310            if func.block_of(inst).is_none() {
311                return Some(Refusal::Gone(inst));
312            }
313        }
314        for (at, carried) in self.carries.iter().enumerate() {
315            let edge = (carried.from, carried.at);
316            if self.carries[..at].iter().any(|other| (other.from, other.at) == edge) {
317                return Some(Refusal::Edge(carried.from, carried.at));
318            }
319            let Some(call) = func[carried.from].succs.get(carried.at) else {
320                return Some(Refusal::Edge(carried.from, carried.at));
321            };
322            if func[call.block].params.len() != carried.args.len() {
323                return Some(Refusal::Args(carried.from, carried.at));
324            }
325        }
326        for &(inst, ref what) in &self.changes {
327            match what {
328                What::Rewrite(plan) => {
329                    if let Some(refusal) = shaped(inst, plan, names, machine) {
330                        return Some(refusal);
331                    }
332                }
333                What::Rename { from, into } => {
334                    if let Some(refusal) = renamed(func, inst, *from, *into) {
335                        return Some(refusal);
336                    }
337                }
338                What::Remove => {
339                    if self.read_after(func, reads, inst) {
340                        return Some(Refusal::Read(inst));
341                    }
342                }
343            }
344        }
345        None
346    }
347
348    /// Takes the set if the target and the function will have it, and gives back how many changes
349    /// it made.
350    ///
351    /// # Errors
352    ///
353    /// The first [`Refusal`] the set earns, with nothing written. A refused set leaves the
354    /// function exactly as it was.
355    pub fn commit(
356        self,
357        func: &mut mir::Func,
358        reads: &mut Reads,
359        names: &Interner,
360        machine: &MachineInsts,
361    ) -> Result<usize, Refusal> {
362        if let Some(refusal) = self.refused(func, reads, names, machine) {
363            return Err(refusal);
364        }
365        let touched = self.len();
366        for (inst, what) in self.changes {
367            let (lost, gained) = moved(func, inst, &what);
368            for reg in lost {
369                reads.lost(reg);
370            }
371            for reg in gained {
372                reads.gained(reg);
373            }
374            match what {
375                What::Rewrite(plan) => {
376                    let operands = func.push_operands(&plan.operands);
377                    let imm = plan.imm.map(|value| func.add_imm(value));
378                    let mem = plan.amode.map(|amode| func.add_amode(amode));
379                    let data = &mut func[inst];
380                    data.opcode = plan.opcode;
381                    data.operands = operands;
382                    data.imm = imm;
383                    data.mem = mem;
384                    data.symbol = plan.symbol;
385                }
386                What::Rename { from, into } => {
387                    let operands = func[inst].operands;
388                    for operand in &mut func[operands] {
389                        if operand.role == Role::Use && operand.reg == from {
390                            operand.reg = into;
391                        }
392                    }
393                }
394                What::Remove => func.remove_inst(inst),
395            }
396        }
397        for carried in self.carries {
398            for &arg in &func[carried.from].succs[carried.at].args {
399                reads.lost(arg);
400            }
401            for &arg in &carried.args {
402                reads.gained(arg);
403            }
404            func.succs_mut(carried.from)[carried.at].args = carried.args;
405        }
406        Ok(touched)
407    }
408
409    /// Whether anything the set leaves behind reads a register that instruction writes.
410    ///
411    /// The counts are of the function as it stands, so what the set is about has to be taken off
412    /// them: a read something in the set stops doing is a read that is going, and one it takes up
413    /// is a read that is arriving. What is left after that is the reads nothing in this set is
414    /// doing anything about, and one of those is enough to keep the instruction where it is.
415    fn read_after(&self, func: &mir::Func, reads: &Reads, inst: mir::Inst) -> bool {
416        func[func[inst].operands].iter().filter(|operand| operand.role.is_def()).any(|operand| {
417            let mut left = reads.count(operand.reg);
418            let mut settle = |lost: &[mir::Reg], gained: &[mir::Reg]| {
419                let goes = lost.iter().filter(|&&reg| reg == operand.reg).count();
420                left = left.saturating_sub(goes);
421                left += gained.iter().filter(|&&reg| reg == operand.reg).count();
422            };
423            for &(other, ref what) in &self.changes {
424                let (lost, gained) = moved(func, other, what);
425                settle(&lost, &gained);
426            }
427            for carried in &self.carries {
428                settle(&func[carried.from].succs[carried.at].args, &carried.args);
429            }
430            left != 0
431        })
432    }
433}
434
435/// The reads one change takes away from its instruction and the reads it gives it.
436///
437/// Of the instruction as it stands, since that is what the counts being kept up to date are of. A
438/// plan is the whole operand vector, so every read the instruction had goes and every read the plan
439/// has arrives. A rename is the reads of the one register, which become that many of the other. A
440/// removal is every read it had and nothing back.
441fn moved(func: &mir::Func, inst: mir::Inst, what: &What) -> (Vec<mir::Reg>, Vec<mir::Reg>) {
442    let held: Vec<mir::Reg> = func[func[inst].operands]
443        .iter()
444        .filter(|operand| operand.role == Role::Use)
445        .map(|operand| operand.reg)
446        .collect();
447    match what {
448        What::Rewrite(plan) => (held, plan.reads().collect()),
449        What::Rename { from, into } => {
450            let gone: Vec<mir::Reg> = held.into_iter().filter(|reg| reg == from).collect();
451            let back = vec![*into; gone.len()];
452            (gone, back)
453        }
454        What::Remove => (held, Vec::new()),
455    }
456}
457
458/// Why a rename would not be taken, or `None` if it would.
459///
460/// A rename changes no shape, so the description has nothing to say about it beyond the one thing
461/// it says about every register operand, which is the class. A physical register has no class in
462/// the function to check against, and by the time there are any of those the allocator has already
463/// had the say about which registers an instruction may name.
464fn renamed(func: &mir::Func, inst: mir::Inst, from: mir::Reg, into: mir::Reg) -> Option<Refusal> {
465    let class = func.class_of(into)?;
466    func[func[inst].operands]
467        .iter()
468        .any(|operand| operand.role == Role::Use && operand.reg == from && operand.class != class)
469        .then_some(Refusal::Class(inst))
470}
471
472/// Why the target would not have that instruction, or `None` if it would.
473///
474/// The description says which operands the instruction has, which is the ones it writes and then
475/// the ones it reads, and it stops there: the registers an addressing mode names are operands the
476/// addressing mode knows the positions of, so what is checked about those is that they are reads,
477/// that the mode points at them, and that there are no others.
478///
479/// The constraint is checked along with the class and the role, because it is part of what the
480/// instruction is rather than part of what a pass may choose. An `add` on this machine writes its
481/// answer into the register it read, the description says so with a reuse constraint, and a
482/// proposal that leaves the constraint off is asking for an instruction this machine has no
483/// encoding for. Nothing downstream would catch it either: the allocator gives an operand whatever
484/// its constraint asks for, so a missing constraint is a register pair that is allocated apart and
485/// then printed as one instruction.
486fn shaped(
487    inst: mir::Inst,
488    plan: &Plan,
489    names: &Interner,
490    machine: &MachineInsts,
491) -> Option<Refusal> {
492    let name = names.resolve(plan.opcode.name());
493    let bare = machine.bare(name);
494    let Some(desc) = (machine.operands)(bare) else { return Some(Refusal::Unknown(inst)) };
495    if plan.operands.len() < desc.len() {
496        return Some(Refusal::Operands(inst));
497    }
498    let (described, addressed) = plan.operands.split_at(desc.len());
499    for (operand, want) in described.iter().zip(desc) {
500        let shape = (operand.class, operand.role, operand.constraint);
501        if shape != (want.class, want.role, want.constraint) {
502            return Some(Refusal::Operands(inst));
503        }
504    }
505    if plan.imm.is_some() != (machine.takes_imm)(bare) {
506        return Some(Refusal::Imm(inst));
507    }
508    let Some(amode) = plan.amode else {
509        return ((machine.takes_mem)(bare) || !addressed.is_empty()).then_some(Refusal::Mem(inst));
510    };
511    if !(machine.takes_mem)(bare) {
512        return Some(Refusal::Mem(inst));
513    }
514    let named = [amode.base, amode.index].into_iter().flatten();
515    let mut wanted = 0;
516    for at in named {
517        let Some(operand) = plan.operands.get(usize::from(at)) else {
518            return Some(Refusal::Operands(inst));
519        };
520        if usize::from(at) < desc.len() || operand.role != Role::Use {
521            return Some(Refusal::Operands(inst));
522        }
523        wanted += 1;
524    }
525    if addressed.len() != wanted {
526        return Some(Refusal::Operands(inst));
527    }
528    let scaled = if amode.index.is_some() { amode.scale } else { 1 };
529    if !machine.scales(scaled) || (amode.index.is_none() && amode.scale != 1) {
530        return Some(Refusal::Scale(inst));
531    }
532    None
533}
534
535#[cfg(test)]
536mod tests {
537    use rucc_mir::Constraint;
538    use rucc_target::x86_64::{GPR, MACHINE, XMM};
539
540    use super::*;
541
542    /// A function with one block, and the names it was built with.
543    fn empty() -> (Interner, mir::Func, mir::Block) {
544        let mut names = Interner::new();
545        let mut func = mir::Func::new(names.intern("f"));
546        let block = func.create_block();
547        (names, func, block)
548    }
549
550    /// The opcode of that name on this target.
551    fn op(names: &mut Interner, name: &str) -> mir::Opcode {
552        mir::Opcode::new(names.intern(&format!("{}{name}", MACHINE.prefix)))
553    }
554
555    /// Whether the target and the function would have the set.
556    fn refused(func: &mir::Func, names: &Interner, set: &Changes) -> Option<Refusal> {
557        set.refused(func, &Reads::of(func), names, &MACHINE)
558    }
559
560    /// What every instruction in a block came to, as opcodes.
561    fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<String> {
562        func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
563    }
564
565    /// A copy, which is the smallest instruction with an operand of each kind.
566    fn copy(
567        func: &mut mir::Func,
568        names: &mut Interner,
569        block: mir::Block,
570    ) -> (mir::Inst, mir::Reg) {
571        let from = func.new_vreg(GPR);
572        let into = func.new_vreg(GPR);
573        let mov = op(names, "mov_rr_64");
574        (func.build(block, mov).def(into, GPR).uses(from, GPR).finish(), into)
575    }
576
577    /// The shape the whole thing is for: an instruction becomes another the target has, and the
578    /// function says so afterwards.
579    #[test]
580    fn a_rewrite_the_target_has_is_taken() {
581        let (mut names, mut func, block) = empty();
582        let (mov, into) = copy(&mut func, &mut names, block);
583        let base = func.new_vreg(GPR);
584        let load = op(&mut names, "mov_rm_64");
585        let mut set = Changes::new();
586        set.rewrite(
587            mov,
588            Plan {
589                opcode: load,
590                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(base, GPR)],
591                imm: None,
592                amode: Some(mir::Amode {
593                    base: Some(1),
594                    index: None,
595                    scale: 1,
596                    disp: 8,
597                    symbol: None,
598                    block: None,
599                    table: None,
600                    reach: mir::Reach::Itself,
601                    segment: None,
602                }),
603                symbol: None,
604            },
605        );
606
607        let mut reads = Reads::of(&func);
608        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
609
610        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
611        assert_eq!(func[func[mov].mem.expect("the load has an address")].disp, 8);
612        assert_eq!(reads.count(base), 1, "the address register is read now");
613        assert_eq!(reads.count(into), 0, "the register the copy read is read by nothing");
614    }
615
616    /// An opcode nobody described. The proposal is the pass's mistake rather than the target's, and
617    /// this is where it stops.
618    #[test]
619    fn an_opcode_this_target_does_not_have_is_refused() {
620        let (mut names, mut func, block) = empty();
621        let (mov, into) = copy(&mut func, &mut names, block);
622        let made_up = op(&mut names, "mov_rr_65");
623        let mut set = Changes::new();
624        set.rewrite(
625            mov,
626            Plan {
627                opcode: made_up,
628                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
629                imm: None,
630                amode: None,
631                symbol: None,
632            },
633        );
634
635        assert_eq!(refused(&func, &names, &set), Some(Refusal::Unknown(mov)));
636        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"], "the function was written to");
637    }
638
639    /// An operand of the wrong class. The target's description of a copy between general registers
640    /// says both are general registers, and a proposal that reads a vector register is a different
641    /// instruction with the same name.
642    #[test]
643    fn an_operand_of_the_wrong_class_is_refused() {
644        let (mut names, mut func, block) = empty();
645        let (mov, into) = copy(&mut func, &mut names, block);
646        let float = func.new_vreg(XMM);
647        let same = func[mov].opcode;
648        let mut set = Changes::new();
649        set.rewrite(
650            mov,
651            Plan {
652                opcode: same,
653                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(float, XMM)],
654                imm: None,
655                amode: None,
656                symbol: None,
657            },
658        );
659
660        assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
661    }
662
663    /// The constraint is part of the instruction. An `add` writes its answer where it read its
664    /// first operand, and a proposal that leaves that off is asking for an encoding this machine
665    /// does not have.
666    #[test]
667    fn an_add_whose_answer_is_not_tied_to_its_source_is_refused() {
668        let (mut names, mut func, block) = empty();
669        let (mov, into) = copy(&mut func, &mut names, block);
670        let other = func.new_vreg(GPR);
671        let add = op(&mut names, "add_rr_64");
672        let loose = vec![
673            mir::Operand::write(into, GPR),
674            mir::Operand::read(into, GPR),
675            mir::Operand::read(other, GPR),
676        ];
677        let mut set = Changes::new();
678        set.rewrite(
679            mov,
680            Plan { opcode: add, operands: loose.clone(), imm: None, amode: None, symbol: None },
681        );
682        assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
683
684        let mut tied = loose;
685        tied[0] = tied[0].with(Constraint::Reuse(1));
686        let mut set = Changes::new();
687        set.rewrite(
688            mov,
689            Plan { opcode: add, operands: tied, imm: None, amode: None, symbol: None },
690        );
691        assert_eq!(refused(&func, &names, &set), None, "the same instruction written properly");
692    }
693
694    /// An immediate belongs to the instructions that carry one, and to no others. Both ways round,
695    /// because a pass that drops an immediate is as wrong as one that invents it.
696    #[test]
697    fn an_immediate_has_to_be_there_exactly_when_the_instruction_carries_one() {
698        let (mut names, mut func, block) = empty();
699        let (mov, into) = copy(&mut func, &mut names, block);
700        let same = func[mov].opcode;
701        let add = op(&mut names, "add_ri_64");
702        let mut set = Changes::new();
703        set.rewrite(
704            mov,
705            Plan {
706                opcode: same,
707                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
708                imm: Some(7),
709                amode: None,
710                symbol: None,
711            },
712        );
713        assert_eq!(refused(&func, &names, &set), Some(Refusal::Imm(mov)), "a copy of seven");
714
715        let tied = vec![
716            mir::Operand::write(into, GPR).with(Constraint::Reuse(1)),
717            mir::Operand::read(into, GPR),
718        ];
719        let mut set = Changes::new();
720        set.rewrite(
721            mov,
722            Plan { opcode: add, operands: tied.clone(), imm: None, amode: None, symbol: None },
723        );
724        assert_eq!(refused(&func, &names, &set), Some(Refusal::Imm(mov)), "an add of nothing");
725
726        let mut set = Changes::new();
727        set.rewrite(
728            mov,
729            Plan { opcode: add, operands: tied, imm: Some(7), amode: None, symbol: None },
730        );
731        assert_eq!(refused(&func, &names, &set), None);
732    }
733
734    /// An address belongs to the instructions that have one. A copy with an address is a load and
735    /// has a different name, which is exactly the mistake a pass folding addresses can make.
736    #[test]
737    fn an_address_on_an_instruction_that_has_none_is_refused() {
738        let (mut names, mut func, block) = empty();
739        let (mov, into) = copy(&mut func, &mut names, block);
740        let base = func.new_vreg(GPR);
741        let same = func[mov].opcode;
742        let mut set = Changes::new();
743        set.rewrite(
744            mov,
745            Plan {
746                opcode: same,
747                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(base, GPR)],
748                imm: None,
749                amode: Some(mir::Amode {
750                    base: Some(1),
751                    index: None,
752                    scale: 1,
753                    disp: 0,
754                    symbol: None,
755                    block: None,
756                    table: None,
757                    reach: mir::Reach::Itself,
758                    segment: None,
759                }),
760                symbol: None,
761            },
762        );
763
764        assert_eq!(refused(&func, &names, &set), Some(Refusal::Mem(mov)));
765    }
766
767    /// A load with no address at all, which is the same mistake the other way round.
768    #[test]
769    fn an_instruction_that_wants_an_address_and_has_none_is_refused() {
770        let (mut names, mut func, block) = empty();
771        let (mov, into) = copy(&mut func, &mut names, block);
772        let load = op(&mut names, "mov_rm_64");
773        let mut set = Changes::new();
774        set.rewrite(
775            mov,
776            Plan {
777                opcode: load,
778                operands: vec![mir::Operand::write(into, GPR)],
779                imm: None,
780                amode: None,
781                symbol: None,
782            },
783        );
784
785        assert_eq!(refused(&func, &names, &set), Some(Refusal::Mem(mov)));
786    }
787
788    /// A scale this machine cannot write. Folding an address into a reader is where a number like
789    /// this is worked out, and three is what a multiplication by three looks like halfway through
790    /// the fold.
791    #[test]
792    fn an_index_scaled_by_something_this_machine_cannot_write_is_refused() {
793        let (mut names, mut func, block) = empty();
794        let (mov, into) = copy(&mut func, &mut names, block);
795        let base = func.new_vreg(GPR);
796        let index = func.new_vreg(GPR);
797        let load = op(&mut names, "mov_rm_64");
798        let scaled = |scale| Plan {
799            opcode: load,
800            operands: vec![
801                mir::Operand::write(into, GPR),
802                mir::Operand::read(base, GPR),
803                mir::Operand::read(index, GPR),
804            ],
805            imm: None,
806            amode: Some(mir::Amode {
807                base: Some(1),
808                index: Some(2),
809                scale,
810                disp: 0,
811                symbol: None,
812                block: None,
813                table: None,
814                reach: mir::Reach::Itself,
815                segment: None,
816            }),
817            symbol: None,
818        };
819        let mut set = Changes::new();
820        set.rewrite(mov, scaled(3));
821        assert_eq!(refused(&func, &names, &set), Some(Refusal::Scale(mov)));
822
823        let mut set = Changes::new();
824        set.rewrite(mov, scaled(4));
825        assert_eq!(refused(&func, &names, &set), None);
826    }
827
828    /// An address whose base points at an operand the description already claimed. The registers an
829    /// address names come after the ones the instruction itself has, and a mode pointing into the
830    /// middle of the others is a printer's mistake waiting to happen.
831    #[test]
832    fn an_address_pointing_at_an_operand_of_its_own_instruction_is_refused() {
833        let (mut names, mut func, block) = empty();
834        let (mov, into) = copy(&mut func, &mut names, block);
835        let load = op(&mut names, "mov_rm_64");
836        let mut set = Changes::new();
837        set.rewrite(
838            mov,
839            Plan {
840                opcode: load,
841                operands: vec![mir::Operand::write(into, GPR)],
842                imm: None,
843                amode: Some(mir::Amode {
844                    base: Some(0),
845                    index: None,
846                    scale: 1,
847                    disp: 0,
848                    symbol: None,
849                    block: None,
850                    table: None,
851                    reach: mir::Reach::Itself,
852                    segment: None,
853                }),
854                symbol: None,
855            },
856        );
857
858        assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
859    }
860
861    /// The question the set is for. Taking an instruction out while something still reads what it
862    /// wrote is the mistake every pass that removes instructions can make, and this is the one
863    /// place it is answered.
864    #[test]
865    fn removing_an_instruction_whose_answer_is_still_read_is_refused() {
866        let (mut names, mut func, block) = empty();
867        let (mov, into) = copy(&mut func, &mut names, block);
868        let out = func.new_vreg(GPR);
869        let second = func[mov].opcode;
870        func.build(block, second).def(out, GPR).uses(into, GPR).finish();
871        let mut set = Changes::new();
872        set.remove(mov);
873
874        assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(mov)));
875        assert_eq!(shape(&func, &names, block).len(), 2);
876    }
877
878    /// The same removal in a set that deals with the reader as well. This is what a fold is: the
879    /// address goes because the instruction that read it does not read it any more, and neither
880    /// half is worth doing without the other.
881    #[test]
882    fn removing_it_in_a_set_that_takes_away_the_reader_is_taken() {
883        let (mut names, mut func, block) = empty();
884        let base = func.new_vreg(GPR);
885        let address = func.new_vreg(GPR);
886        let out = func.new_vreg(GPR);
887        let lea = op(&mut names, "lea_64");
888        let load = op(&mut names, "mov_rm_64");
889        let at = |reg| mir::Mem { disp: 16, ..mir::Mem::at(mir::Operand::read(reg, GPR)) };
890        let made = func.build(block, lea).def(address, GPR).mem(at(base)).finish();
891        let read = func
892            .build(block, load)
893            .def(out, GPR)
894            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
895            .finish();
896
897        let mut set = Changes::new();
898        set.rewrite(
899            read,
900            Plan {
901                opcode: load,
902                operands: vec![mir::Operand::write(out, GPR), mir::Operand::read(base, GPR)],
903                imm: None,
904                amode: Some(mir::Amode { disp: 16, ..func[func[read].mem.expect("a load")] }),
905                symbol: None,
906            },
907        );
908        set.remove(made);
909
910        let mut reads = Reads::of(&func);
911        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
912
913        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
914        assert_eq!(reads.count(address), 0, "nothing reads the address the lea wrote");
915        assert_eq!(reads.count(base), 1, "the load reads what the lea read");
916    }
917
918    /// A rename with the removal it is there for, which is the shape every pass that takes an
919    /// instruction out and sends its readers somewhere else hands over.
920    #[test]
921    fn a_rename_that_takes_the_last_reader_off_an_instruction_lets_it_go() {
922        let (mut names, mut func, block) = empty();
923        let (first, into) = copy(&mut func, &mut names, block);
924        let source = func[func[first].operands][1].reg;
925        let out = func.new_vreg(GPR);
926        let mov = func[first].opcode;
927        let second = func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
928
929        let mut set = Changes::new();
930        set.rename(second, into, source);
931        set.remove(first);
932        let mut reads = Reads::of(&func);
933        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
934
935        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"]);
936        assert_eq!(func[func[second].operands][1].reg, source, "the reader was not sent on");
937        assert_eq!(reads.count(into), 0, "nothing reads what the copy wrote");
938        assert_eq!(reads.count(source), 1, "the reader reads what the copy read");
939    }
940
941    /// The same removal with the rename left out, which is the mistake the set is there to catch.
942    #[test]
943    fn a_removal_whose_reader_is_not_renamed_is_refused() {
944        let (mut names, mut func, block) = empty();
945        let (first, into) = copy(&mut func, &mut names, block);
946        let out = func.new_vreg(GPR);
947        let mov = func[first].opcode;
948        func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
949
950        let mut set = Changes::new();
951        set.remove(first);
952        assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(first)));
953    }
954
955    /// A rename that would have an instruction read a register of another class. The operand says
956    /// which class it is, the function says which class the register is, and an instruction whose
957    /// operands disagree with that is one the allocator has no registers for.
958    #[test]
959    fn a_rename_into_a_register_of_another_class_is_refused() {
960        let (mut names, mut func, block) = empty();
961        let (mov, _) = copy(&mut func, &mut names, block);
962        let source = func[func[mov].operands][1].reg;
963        let float = func.new_vreg(XMM);
964
965        let mut set = Changes::new();
966        set.rename(mov, source, float);
967        assert_eq!(refused(&func, &names, &set), Some(Refusal::Class(mov)));
968    }
969
970    /// What an edge carries is where the answer of a conversion in one block reaches a reader in
971    /// another, and sending it somewhere else is the same change as renaming an operand.
972    #[test]
973    fn an_edge_carries_what_the_set_says_and_the_instruction_it_read_goes() {
974        let (mut names, mut func, block) = empty();
975        let next = func.create_block();
976        let (mov, into) = copy(&mut func, &mut names, block);
977        let source = func[func[mov].operands][1].reg;
978        let arrived = func.new_vreg(GPR);
979        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
980        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
981
982        let mut set = Changes::new();
983        set.carry(block, 0, vec![source]);
984        set.remove(mov);
985        let mut reads = Reads::of(&func);
986        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
987
988        assert!(shape(&func, &names, block).is_empty(), "the copy is still there");
989        assert_eq!(func[block].succs[0].args, vec![source]);
990        assert_eq!(reads.count(into), 0);
991        assert_eq!(reads.count(source), 1, "the edge reads what the copy read");
992    }
993
994    /// An edge that block does not have, which is a set built against a function that has been
995    /// laid out since.
996    #[test]
997    fn an_edge_that_is_not_there_is_refused() {
998        let (mut names, mut func, block) = empty();
999        copy(&mut func, &mut names, block);
1000
1001        let mut set = Changes::new();
1002        set.carry(block, 0, Vec::new());
1003        assert_eq!(refused(&func, &names, &set), Some(Refusal::Edge(block, 0)));
1004    }
1005
1006    /// An edge carrying a different number of values than the block it goes to takes. The
1007    /// parameters are where they arrive, so one that arrives nowhere is a function nothing after
1008    /// this could read.
1009    #[test]
1010    fn an_edge_carrying_the_wrong_number_of_values_is_refused() {
1011        let (mut names, mut func, block) = empty();
1012        let next = func.create_block();
1013        let (_, into) = copy(&mut func, &mut names, block);
1014        let arrived = func.new_vreg(GPR);
1015        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
1016        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
1017
1018        let mut set = Changes::new();
1019        set.carry(block, 0, vec![into, into]);
1020        assert_eq!(refused(&func, &names, &set), Some(Refusal::Args(block, 0)));
1021    }
1022
1023    /// An argument an edge carries is a read like any other and is in no operand vector, which is
1024    /// the one place a count of reads is easy to get wrong.
1025    #[test]
1026    fn an_answer_an_edge_carries_keeps_its_instruction() {
1027        let (mut names, mut func, block) = empty();
1028        let next = func.create_block();
1029        let (mov, into) = copy(&mut func, &mut names, block);
1030        let arrived = func.new_vreg(GPR);
1031        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
1032        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
1033        let mut set = Changes::new();
1034        set.remove(mov);
1035
1036        assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(mov)));
1037    }
1038
1039    /// One instruction, two minds. A set that says an instruction becomes one thing and then
1040    /// another is a pass that has lost track of what it proposed, and taking either half would be
1041    /// picking for it.
1042    #[test]
1043    fn an_instruction_named_twice_is_refused() {
1044        let (mut names, mut func, block) = empty();
1045        let (mov, _) = copy(&mut func, &mut names, block);
1046        let mut set = Changes::new();
1047        set.rewrite(mov, Plan::of(&func, mov));
1048        set.remove(mov);
1049
1050        assert_eq!(refused(&func, &names, &set), Some(Refusal::Twice(mov)));
1051    }
1052
1053    /// A set built against a function that has moved on since. The instruction it names is gone,
1054    /// and a rewrite of an instruction in no block would be a rewrite nothing ever runs.
1055    #[test]
1056    fn an_instruction_that_has_already_gone_is_refused() {
1057        let (mut names, mut func, block) = empty();
1058        let (mov, _) = copy(&mut func, &mut names, block);
1059        let plan = Plan::of(&func, mov);
1060        func.remove_inst(mov);
1061        let mut set = Changes::new();
1062        set.rewrite(mov, plan);
1063
1064        assert_eq!(refused(&func, &names, &set), Some(Refusal::Gone(mov)));
1065    }
1066
1067    /// The instruction as it stands is a proposal that changes nothing, which is what a pass that
1068    /// rewrites one operand starts from.
1069    #[test]
1070    fn the_instruction_as_it_stands_is_a_proposal_the_target_takes() {
1071        let (mut names, mut func, block) = empty();
1072        let base = func.new_vreg(GPR);
1073        let out = func.new_vreg(GPR);
1074        let load = op(&mut names, "mov_rm_64");
1075        let read = func
1076            .build(block, load)
1077            .def(out, GPR)
1078            .mem(mir::Mem { disp: 24, ..mir::Mem::at(mir::Operand::read(base, GPR)) })
1079            .finish();
1080
1081        let plan = Plan::of(&func, read);
1082        let mut set = Changes::new();
1083        set.rewrite(read, plan.clone());
1084        let mut reads = Reads::of(&func);
1085        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
1086
1087        assert_eq!(Plan::of(&func, read), plan);
1088        assert_eq!(reads.count(base), 1, "the one read it had before");
1089    }
1090
1091    /// A refused set writes nothing, including the half of it that would have been fine. That is
1092    /// the whole point of proposing a set rather than applying instructions one at a time.
1093    #[test]
1094    fn a_set_with_one_bad_change_in_it_leaves_the_others_alone() {
1095        let (mut names, mut func, block) = empty();
1096        let (first, into) = copy(&mut func, &mut names, block);
1097        let (second, _) = copy(&mut func, &mut names, block);
1098        let load = op(&mut names, "mov_rm_64");
1099        let mut set = Changes::new();
1100        set.rewrite(
1101            first,
1102            Plan {
1103                opcode: load,
1104                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
1105                imm: None,
1106                amode: Some(mir::Amode {
1107                    base: Some(1),
1108                    index: None,
1109                    scale: 1,
1110                    disp: 0,
1111                    symbol: None,
1112                    block: None,
1113                    table: None,
1114                    reach: mir::Reach::Itself,
1115                    segment: None,
1116                }),
1117                symbol: None,
1118            },
1119        );
1120        set.rewrite(second, Plan { imm: Some(3), ..Plan::of(&func, second) });
1121
1122        let mut reads = Reads::of(&func);
1123        assert_eq!(
1124            set.commit(&mut func, &mut reads, &names, &MACHINE),
1125            Err(Refusal::Imm(second)),
1126            "the second change is the one the target turns down"
1127        );
1128        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64", "x64.mov_rr_64"]);
1129    }
1130
1131    /// A set with nothing in it, which is what a pass that found nothing to do hands over.
1132    #[test]
1133    fn a_set_with_nothing_in_it_commits() {
1134        let (mut names, mut func, block) = empty();
1135        copy(&mut func, &mut names, block);
1136        let set = Changes::new();
1137        assert!(set.is_empty());
1138
1139        let mut reads = Reads::of(&func);
1140        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(0));
1141        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"]);
1142    }
1143
1144    /// The counts a pass carries from one commit to the next. A removal the first commit makes
1145    /// possible has to be a removal the second commit agrees to, and it only is if the count came
1146    /// down when the reader went.
1147    #[test]
1148    fn the_counts_are_still_right_after_a_commit() {
1149        let (mut names, mut func, block) = empty();
1150        let (first, into) = copy(&mut func, &mut names, block);
1151        let out = func.new_vreg(GPR);
1152        let mov = func[first].opcode;
1153        let second = func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
1154
1155        let mut reads = Reads::of(&func);
1156        assert_eq!(reads.count(into), 1);
1157        let mut set = Changes::new();
1158        set.remove(second);
1159        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
1160        assert_eq!(reads.count(into), 0, "the reader went and the count went with it");
1161
1162        let mut set = Changes::new();
1163        set.remove(first);
1164        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
1165        assert!(shape(&func, &names, block).is_empty());
1166    }
1167}