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                    reach: mir::Reach::Itself,
600                    segment: None,
601                }),
602                symbol: None,
603            },
604        );
605
606        let mut reads = Reads::of(&func);
607        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
608
609        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
610        assert_eq!(func[func[mov].mem.expect("the load has an address")].disp, 8);
611        assert_eq!(reads.count(base), 1, "the address register is read now");
612        assert_eq!(reads.count(into), 0, "the register the copy read is read by nothing");
613    }
614
615    /// An opcode nobody described. The proposal is the pass's mistake rather than the target's, and
616    /// this is where it stops.
617    #[test]
618    fn an_opcode_this_target_does_not_have_is_refused() {
619        let (mut names, mut func, block) = empty();
620        let (mov, into) = copy(&mut func, &mut names, block);
621        let made_up = op(&mut names, "mov_rr_65");
622        let mut set = Changes::new();
623        set.rewrite(
624            mov,
625            Plan {
626                opcode: made_up,
627                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
628                imm: None,
629                amode: None,
630                symbol: None,
631            },
632        );
633
634        assert_eq!(refused(&func, &names, &set), Some(Refusal::Unknown(mov)));
635        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"], "the function was written to");
636    }
637
638    /// An operand of the wrong class. The target's description of a copy between general registers
639    /// says both are general registers, and a proposal that reads a vector register is a different
640    /// instruction with the same name.
641    #[test]
642    fn an_operand_of_the_wrong_class_is_refused() {
643        let (mut names, mut func, block) = empty();
644        let (mov, into) = copy(&mut func, &mut names, block);
645        let float = func.new_vreg(XMM);
646        let same = func[mov].opcode;
647        let mut set = Changes::new();
648        set.rewrite(
649            mov,
650            Plan {
651                opcode: same,
652                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(float, XMM)],
653                imm: None,
654                amode: None,
655                symbol: None,
656            },
657        );
658
659        assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
660    }
661
662    /// The constraint is part of the instruction. An `add` writes its answer where it read its
663    /// first operand, and a proposal that leaves that off is asking for an encoding this machine
664    /// does not have.
665    #[test]
666    fn an_add_whose_answer_is_not_tied_to_its_source_is_refused() {
667        let (mut names, mut func, block) = empty();
668        let (mov, into) = copy(&mut func, &mut names, block);
669        let other = func.new_vreg(GPR);
670        let add = op(&mut names, "add_rr_64");
671        let loose = vec![
672            mir::Operand::write(into, GPR),
673            mir::Operand::read(into, GPR),
674            mir::Operand::read(other, GPR),
675        ];
676        let mut set = Changes::new();
677        set.rewrite(
678            mov,
679            Plan { opcode: add, operands: loose.clone(), imm: None, amode: None, symbol: None },
680        );
681        assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
682
683        let mut tied = loose;
684        tied[0] = tied[0].with(Constraint::Reuse(1));
685        let mut set = Changes::new();
686        set.rewrite(
687            mov,
688            Plan { opcode: add, operands: tied, imm: None, amode: None, symbol: None },
689        );
690        assert_eq!(refused(&func, &names, &set), None, "the same instruction written properly");
691    }
692
693    /// An immediate belongs to the instructions that carry one, and to no others. Both ways round,
694    /// because a pass that drops an immediate is as wrong as one that invents it.
695    #[test]
696    fn an_immediate_has_to_be_there_exactly_when_the_instruction_carries_one() {
697        let (mut names, mut func, block) = empty();
698        let (mov, into) = copy(&mut func, &mut names, block);
699        let same = func[mov].opcode;
700        let add = op(&mut names, "add_ri_64");
701        let mut set = Changes::new();
702        set.rewrite(
703            mov,
704            Plan {
705                opcode: same,
706                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
707                imm: Some(7),
708                amode: None,
709                symbol: None,
710            },
711        );
712        assert_eq!(refused(&func, &names, &set), Some(Refusal::Imm(mov)), "a copy of seven");
713
714        let tied = vec![
715            mir::Operand::write(into, GPR).with(Constraint::Reuse(1)),
716            mir::Operand::read(into, GPR),
717        ];
718        let mut set = Changes::new();
719        set.rewrite(
720            mov,
721            Plan { opcode: add, operands: tied.clone(), imm: None, amode: None, symbol: None },
722        );
723        assert_eq!(refused(&func, &names, &set), Some(Refusal::Imm(mov)), "an add of nothing");
724
725        let mut set = Changes::new();
726        set.rewrite(
727            mov,
728            Plan { opcode: add, operands: tied, imm: Some(7), amode: None, symbol: None },
729        );
730        assert_eq!(refused(&func, &names, &set), None);
731    }
732
733    /// An address belongs to the instructions that have one. A copy with an address is a load and
734    /// has a different name, which is exactly the mistake a pass folding addresses can make.
735    #[test]
736    fn an_address_on_an_instruction_that_has_none_is_refused() {
737        let (mut names, mut func, block) = empty();
738        let (mov, into) = copy(&mut func, &mut names, block);
739        let base = func.new_vreg(GPR);
740        let same = func[mov].opcode;
741        let mut set = Changes::new();
742        set.rewrite(
743            mov,
744            Plan {
745                opcode: same,
746                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(base, GPR)],
747                imm: None,
748                amode: Some(mir::Amode {
749                    base: Some(1),
750                    index: None,
751                    scale: 1,
752                    disp: 0,
753                    symbol: None,
754                    block: None,
755                    reach: mir::Reach::Itself,
756                    segment: None,
757                }),
758                symbol: None,
759            },
760        );
761
762        assert_eq!(refused(&func, &names, &set), Some(Refusal::Mem(mov)));
763    }
764
765    /// A load with no address at all, which is the same mistake the other way round.
766    #[test]
767    fn an_instruction_that_wants_an_address_and_has_none_is_refused() {
768        let (mut names, mut func, block) = empty();
769        let (mov, into) = copy(&mut func, &mut names, block);
770        let load = op(&mut names, "mov_rm_64");
771        let mut set = Changes::new();
772        set.rewrite(
773            mov,
774            Plan {
775                opcode: load,
776                operands: vec![mir::Operand::write(into, GPR)],
777                imm: None,
778                amode: None,
779                symbol: None,
780            },
781        );
782
783        assert_eq!(refused(&func, &names, &set), Some(Refusal::Mem(mov)));
784    }
785
786    /// A scale this machine cannot write. Folding an address into a reader is where a number like
787    /// this is worked out, and three is what a multiplication by three looks like halfway through
788    /// the fold.
789    #[test]
790    fn an_index_scaled_by_something_this_machine_cannot_write_is_refused() {
791        let (mut names, mut func, block) = empty();
792        let (mov, into) = copy(&mut func, &mut names, block);
793        let base = func.new_vreg(GPR);
794        let index = func.new_vreg(GPR);
795        let load = op(&mut names, "mov_rm_64");
796        let scaled = |scale| Plan {
797            opcode: load,
798            operands: vec![
799                mir::Operand::write(into, GPR),
800                mir::Operand::read(base, GPR),
801                mir::Operand::read(index, GPR),
802            ],
803            imm: None,
804            amode: Some(mir::Amode {
805                base: Some(1),
806                index: Some(2),
807                scale,
808                disp: 0,
809                symbol: None,
810                block: None,
811                reach: mir::Reach::Itself,
812                segment: None,
813            }),
814            symbol: None,
815        };
816        let mut set = Changes::new();
817        set.rewrite(mov, scaled(3));
818        assert_eq!(refused(&func, &names, &set), Some(Refusal::Scale(mov)));
819
820        let mut set = Changes::new();
821        set.rewrite(mov, scaled(4));
822        assert_eq!(refused(&func, &names, &set), None);
823    }
824
825    /// An address whose base points at an operand the description already claimed. The registers an
826    /// address names come after the ones the instruction itself has, and a mode pointing into the
827    /// middle of the others is a printer's mistake waiting to happen.
828    #[test]
829    fn an_address_pointing_at_an_operand_of_its_own_instruction_is_refused() {
830        let (mut names, mut func, block) = empty();
831        let (mov, into) = copy(&mut func, &mut names, block);
832        let load = op(&mut names, "mov_rm_64");
833        let mut set = Changes::new();
834        set.rewrite(
835            mov,
836            Plan {
837                opcode: load,
838                operands: vec![mir::Operand::write(into, GPR)],
839                imm: None,
840                amode: Some(mir::Amode {
841                    base: Some(0),
842                    index: None,
843                    scale: 1,
844                    disp: 0,
845                    symbol: None,
846                    block: None,
847                    reach: mir::Reach::Itself,
848                    segment: None,
849                }),
850                symbol: None,
851            },
852        );
853
854        assert_eq!(refused(&func, &names, &set), Some(Refusal::Operands(mov)));
855    }
856
857    /// The question the set is for. Taking an instruction out while something still reads what it
858    /// wrote is the mistake every pass that removes instructions can make, and this is the one
859    /// place it is answered.
860    #[test]
861    fn removing_an_instruction_whose_answer_is_still_read_is_refused() {
862        let (mut names, mut func, block) = empty();
863        let (mov, into) = copy(&mut func, &mut names, block);
864        let out = func.new_vreg(GPR);
865        let second = func[mov].opcode;
866        func.build(block, second).def(out, GPR).uses(into, GPR).finish();
867        let mut set = Changes::new();
868        set.remove(mov);
869
870        assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(mov)));
871        assert_eq!(shape(&func, &names, block).len(), 2);
872    }
873
874    /// The same removal in a set that deals with the reader as well. This is what a fold is: the
875    /// address goes because the instruction that read it does not read it any more, and neither
876    /// half is worth doing without the other.
877    #[test]
878    fn removing_it_in_a_set_that_takes_away_the_reader_is_taken() {
879        let (mut names, mut func, block) = empty();
880        let base = func.new_vreg(GPR);
881        let address = func.new_vreg(GPR);
882        let out = func.new_vreg(GPR);
883        let lea = op(&mut names, "lea_64");
884        let load = op(&mut names, "mov_rm_64");
885        let at = |reg| mir::Mem { disp: 16, ..mir::Mem::at(mir::Operand::read(reg, GPR)) };
886        let made = func.build(block, lea).def(address, GPR).mem(at(base)).finish();
887        let read = func
888            .build(block, load)
889            .def(out, GPR)
890            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
891            .finish();
892
893        let mut set = Changes::new();
894        set.rewrite(
895            read,
896            Plan {
897                opcode: load,
898                operands: vec![mir::Operand::write(out, GPR), mir::Operand::read(base, GPR)],
899                imm: None,
900                amode: Some(mir::Amode { disp: 16, ..func[func[read].mem.expect("a load")] }),
901                symbol: None,
902            },
903        );
904        set.remove(made);
905
906        let mut reads = Reads::of(&func);
907        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
908
909        assert_eq!(shape(&func, &names, block), ["x64.mov_rm_64"]);
910        assert_eq!(reads.count(address), 0, "nothing reads the address the lea wrote");
911        assert_eq!(reads.count(base), 1, "the load reads what the lea read");
912    }
913
914    /// A rename with the removal it is there for, which is the shape every pass that takes an
915    /// instruction out and sends its readers somewhere else hands over.
916    #[test]
917    fn a_rename_that_takes_the_last_reader_off_an_instruction_lets_it_go() {
918        let (mut names, mut func, block) = empty();
919        let (first, into) = copy(&mut func, &mut names, block);
920        let source = func[func[first].operands][1].reg;
921        let out = func.new_vreg(GPR);
922        let mov = func[first].opcode;
923        let second = func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
924
925        let mut set = Changes::new();
926        set.rename(second, into, source);
927        set.remove(first);
928        let mut reads = Reads::of(&func);
929        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
930
931        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"]);
932        assert_eq!(func[func[second].operands][1].reg, source, "the reader was not sent on");
933        assert_eq!(reads.count(into), 0, "nothing reads what the copy wrote");
934        assert_eq!(reads.count(source), 1, "the reader reads what the copy read");
935    }
936
937    /// The same removal with the rename left out, which is the mistake the set is there to catch.
938    #[test]
939    fn a_removal_whose_reader_is_not_renamed_is_refused() {
940        let (mut names, mut func, block) = empty();
941        let (first, into) = copy(&mut func, &mut names, block);
942        let out = func.new_vreg(GPR);
943        let mov = func[first].opcode;
944        func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
945
946        let mut set = Changes::new();
947        set.remove(first);
948        assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(first)));
949    }
950
951    /// A rename that would have an instruction read a register of another class. The operand says
952    /// which class it is, the function says which class the register is, and an instruction whose
953    /// operands disagree with that is one the allocator has no registers for.
954    #[test]
955    fn a_rename_into_a_register_of_another_class_is_refused() {
956        let (mut names, mut func, block) = empty();
957        let (mov, _) = copy(&mut func, &mut names, block);
958        let source = func[func[mov].operands][1].reg;
959        let float = func.new_vreg(XMM);
960
961        let mut set = Changes::new();
962        set.rename(mov, source, float);
963        assert_eq!(refused(&func, &names, &set), Some(Refusal::Class(mov)));
964    }
965
966    /// What an edge carries is where the answer of a conversion in one block reaches a reader in
967    /// another, and sending it somewhere else is the same change as renaming an operand.
968    #[test]
969    fn an_edge_carries_what_the_set_says_and_the_instruction_it_read_goes() {
970        let (mut names, mut func, block) = empty();
971        let next = func.create_block();
972        let (mov, into) = copy(&mut func, &mut names, block);
973        let source = func[func[mov].operands][1].reg;
974        let arrived = func.new_vreg(GPR);
975        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
976        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
977
978        let mut set = Changes::new();
979        set.carry(block, 0, vec![source]);
980        set.remove(mov);
981        let mut reads = Reads::of(&func);
982        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(2));
983
984        assert!(shape(&func, &names, block).is_empty(), "the copy is still there");
985        assert_eq!(func[block].succs[0].args, vec![source]);
986        assert_eq!(reads.count(into), 0);
987        assert_eq!(reads.count(source), 1, "the edge reads what the copy read");
988    }
989
990    /// An edge that block does not have, which is a set built against a function that has been
991    /// laid out since.
992    #[test]
993    fn an_edge_that_is_not_there_is_refused() {
994        let (mut names, mut func, block) = empty();
995        copy(&mut func, &mut names, block);
996
997        let mut set = Changes::new();
998        set.carry(block, 0, Vec::new());
999        assert_eq!(refused(&func, &names, &set), Some(Refusal::Edge(block, 0)));
1000    }
1001
1002    /// An edge carrying a different number of values than the block it goes to takes. The
1003    /// parameters are where they arrive, so one that arrives nowhere is a function nothing after
1004    /// this could read.
1005    #[test]
1006    fn an_edge_carrying_the_wrong_number_of_values_is_refused() {
1007        let (mut names, mut func, block) = empty();
1008        let next = func.create_block();
1009        let (_, into) = copy(&mut func, &mut names, block);
1010        let arrived = func.new_vreg(GPR);
1011        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
1012        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
1013
1014        let mut set = Changes::new();
1015        set.carry(block, 0, vec![into, into]);
1016        assert_eq!(refused(&func, &names, &set), Some(Refusal::Args(block, 0)));
1017    }
1018
1019    /// An argument an edge carries is a read like any other and is in no operand vector, which is
1020    /// the one place a count of reads is easy to get wrong.
1021    #[test]
1022    fn an_answer_an_edge_carries_keeps_its_instruction() {
1023        let (mut names, mut func, block) = empty();
1024        let next = func.create_block();
1025        let (mov, into) = copy(&mut func, &mut names, block);
1026        let arrived = func.new_vreg(GPR);
1027        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
1028        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![into])];
1029        let mut set = Changes::new();
1030        set.remove(mov);
1031
1032        assert_eq!(refused(&func, &names, &set), Some(Refusal::Read(mov)));
1033    }
1034
1035    /// One instruction, two minds. A set that says an instruction becomes one thing and then
1036    /// another is a pass that has lost track of what it proposed, and taking either half would be
1037    /// picking for it.
1038    #[test]
1039    fn an_instruction_named_twice_is_refused() {
1040        let (mut names, mut func, block) = empty();
1041        let (mov, _) = copy(&mut func, &mut names, block);
1042        let mut set = Changes::new();
1043        set.rewrite(mov, Plan::of(&func, mov));
1044        set.remove(mov);
1045
1046        assert_eq!(refused(&func, &names, &set), Some(Refusal::Twice(mov)));
1047    }
1048
1049    /// A set built against a function that has moved on since. The instruction it names is gone,
1050    /// and a rewrite of an instruction in no block would be a rewrite nothing ever runs.
1051    #[test]
1052    fn an_instruction_that_has_already_gone_is_refused() {
1053        let (mut names, mut func, block) = empty();
1054        let (mov, _) = copy(&mut func, &mut names, block);
1055        let plan = Plan::of(&func, mov);
1056        func.remove_inst(mov);
1057        let mut set = Changes::new();
1058        set.rewrite(mov, plan);
1059
1060        assert_eq!(refused(&func, &names, &set), Some(Refusal::Gone(mov)));
1061    }
1062
1063    /// The instruction as it stands is a proposal that changes nothing, which is what a pass that
1064    /// rewrites one operand starts from.
1065    #[test]
1066    fn the_instruction_as_it_stands_is_a_proposal_the_target_takes() {
1067        let (mut names, mut func, block) = empty();
1068        let base = func.new_vreg(GPR);
1069        let out = func.new_vreg(GPR);
1070        let load = op(&mut names, "mov_rm_64");
1071        let read = func
1072            .build(block, load)
1073            .def(out, GPR)
1074            .mem(mir::Mem { disp: 24, ..mir::Mem::at(mir::Operand::read(base, GPR)) })
1075            .finish();
1076
1077        let plan = Plan::of(&func, read);
1078        let mut set = Changes::new();
1079        set.rewrite(read, plan.clone());
1080        let mut reads = Reads::of(&func);
1081        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
1082
1083        assert_eq!(Plan::of(&func, read), plan);
1084        assert_eq!(reads.count(base), 1, "the one read it had before");
1085    }
1086
1087    /// A refused set writes nothing, including the half of it that would have been fine. That is
1088    /// the whole point of proposing a set rather than applying instructions one at a time.
1089    #[test]
1090    fn a_set_with_one_bad_change_in_it_leaves_the_others_alone() {
1091        let (mut names, mut func, block) = empty();
1092        let (first, into) = copy(&mut func, &mut names, block);
1093        let (second, _) = copy(&mut func, &mut names, block);
1094        let load = op(&mut names, "mov_rm_64");
1095        let mut set = Changes::new();
1096        set.rewrite(
1097            first,
1098            Plan {
1099                opcode: load,
1100                operands: vec![mir::Operand::write(into, GPR), mir::Operand::read(into, GPR)],
1101                imm: None,
1102                amode: Some(mir::Amode {
1103                    base: Some(1),
1104                    index: None,
1105                    scale: 1,
1106                    disp: 0,
1107                    symbol: None,
1108                    block: None,
1109                    reach: mir::Reach::Itself,
1110                    segment: None,
1111                }),
1112                symbol: None,
1113            },
1114        );
1115        set.rewrite(second, Plan { imm: Some(3), ..Plan::of(&func, second) });
1116
1117        let mut reads = Reads::of(&func);
1118        assert_eq!(
1119            set.commit(&mut func, &mut reads, &names, &MACHINE),
1120            Err(Refusal::Imm(second)),
1121            "the second change is the one the target turns down"
1122        );
1123        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64", "x64.mov_rr_64"]);
1124    }
1125
1126    /// A set with nothing in it, which is what a pass that found nothing to do hands over.
1127    #[test]
1128    fn a_set_with_nothing_in_it_commits() {
1129        let (mut names, mut func, block) = empty();
1130        copy(&mut func, &mut names, block);
1131        let set = Changes::new();
1132        assert!(set.is_empty());
1133
1134        let mut reads = Reads::of(&func);
1135        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(0));
1136        assert_eq!(shape(&func, &names, block), ["x64.mov_rr_64"]);
1137    }
1138
1139    /// The counts a pass carries from one commit to the next. A removal the first commit makes
1140    /// possible has to be a removal the second commit agrees to, and it only is if the count came
1141    /// down when the reader went.
1142    #[test]
1143    fn the_counts_are_still_right_after_a_commit() {
1144        let (mut names, mut func, block) = empty();
1145        let (first, into) = copy(&mut func, &mut names, block);
1146        let out = func.new_vreg(GPR);
1147        let mov = func[first].opcode;
1148        let second = func.build(block, mov).def(out, GPR).uses(into, GPR).finish();
1149
1150        let mut reads = Reads::of(&func);
1151        assert_eq!(reads.count(into), 1);
1152        let mut set = Changes::new();
1153        set.remove(second);
1154        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
1155        assert_eq!(reads.count(into), 0, "the reader went and the count went with it");
1156
1157        let mut set = Changes::new();
1158        set.remove(first);
1159        assert_eq!(set.commit(&mut func, &mut reads, &names, &MACHINE), Ok(1));
1160        assert!(shape(&func, &names, block).is_empty());
1161    }
1162}