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