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