Skip to main content

rucc_codegen/
bits.rs

1//! Taking out a conversion whose bits nothing reads.
2//!
3//! Design: `spec/optimizer/37-machine-level-optimization.md` section 37.4.
4//!
5//! A register is one register at every width, and what says how much of it is in play is the
6//! instruction naming it. `movzbl %sil, %edi` writes thirty two bits of `rdi` and reads eight of
7//! `rsi`, and if the only thing that ever reads `rdi` is a `movb`, then the twenty four bits the
8//! widening worked out are bits nobody ever looks at. What is left of the widening once those bits
9//! are taken away is a copy of eight bits into a register, which is what the instruction after it
10//! was going to read anyway, so the widening goes and its readers read its source instead.
11//!
12//! That is the bit group liveness of `gcc/ext-dce.cc` at the width this compiler needs it at.
13//! Liveness answers whether a register is read at all, this answers how much of it is read, and
14//! the second question is the first one asked per group of bits rather than per register. Section
15//! 37.4 says to build this one of the two passes it offers, because it is more general than
16//! compare elimination and because the analysis is the liveness the allocator already computes
17//! with a number on it.
18//!
19//! # Where the conversions come from
20//!
21//! Not from code anybody wrote. C promotes nearly every operand of nearly every expression to
22//! `int` before doing anything with it, so a program that adds two `char`s widens both of them,
23//! adds at thirty two bits and stores eight, and the front end writes every one of those
24//! conversions out because each of them is in the language's own description of what the program
25//! means. `crate::widths` and tier four of the rewrite rules take the ones that are two
26//! instructions next to each other in the same block. What is left for this pass is the ones that
27//! are not: a conversion in one block whose readers are in another, and a conversion the selector
28//! itself wrote because the machine instruction it picked wanted its operand at a width the value
29//! did not arrive at.
30//!
31//! # What it finds, measured
32//!
33//! Both directions of conversion are in scope and only one of them turns up, which was not what
34//! was expected and is worth writing down rather than rounding off. Over the 1916 programs of
35//! tamnd/rucc-corpus at `-O2` this takes out 970 instructions and puts back 45, and not one of
36//! the 7040 widenings in that assembly is among them: the count of `movz` and `movs` is the same
37//! before and after. What goes is 469 `movl`, 259 `movw` and 242 `movb` between registers, which
38//! are the narrowings, and the 45 that come back are `movq`, which is the allocator wanting a
39//! plain copy where a narrowing had been doing that job as well as its own.
40//!
41//! That is tier four of the rewrite rules having already been through the corpus. A widening the
42//! rules could not reach is one whose upper bits some reader really does read, and there is
43//! nothing here for a bit counter to find in it. A narrowing is the other way round: the machine
44//! writes one where a value is put in a register at a width, and whether the bits above it matter
45//! is a question about every reader of the result rather than about the pair, which is the
46//! question only this pass asks.
47//!
48//! 2091 bytes of `.text` over the corpus, 76 programs smaller and two larger by a byte each, and
49//! 2048 bytes off SQLite's amalgamation at `-O2`. The two that grow are an eight bit division,
50//! where every narrowing that goes was also the move that got the answer out of the register the
51//! division fixes, so the allocator writes a full width copy of the same pair in its place. Nine
52//! of the ten are the same length either way and the tenth is `movb %dl, %bl` becoming
53//! `movq %rdx, %rbx`, which is the one byte: the byte names of those two registers need no prefix
54//! and the sixty four bit move needs the one that says so.
55//!
56//! # The analysis
57//!
58//! One number per register, which is how many of its low bits anything reads. It starts at none
59//! and grows, so a register nothing has been seen to read yet is one whose answer is still being
60//! worked out rather than one nothing reads.
61//!
62//! Three things raise it. An instruction reading a register raises it to the width that
63//! instruction names the operand at, which is [`rucc_target::BitInsts::width`] and is the target's
64//! answer rather than this pass's. An edge carrying a register into a block raises it to whatever
65//! the parameter it arrives as needs, which is what carries the answer across a block boundary and
66//! is the whole reason this finds anything the rules do not. And an instruction that copies the
67//! low bits of its source raises its source only as far as its own result is read, since the bits
68//! of the source above that are bits it puts nowhere anything reads.
69//!
70//! The last of those is what makes the answer a fixpoint rather than a walk: a chain of
71//! conversions passes the number back along itself, and how far it passes depends on a number the
72//! same pass is still working out. It only ever grows and it is bounded by the widest operand on
73//! the machine, so it settles.
74//!
75//! # What it will not do
76//!
77//! An operand the target's description does not name at a width. An address register, an operand
78//! of an opcode written as no instruction at all, and an opcode from somewhere other than this
79//! target all answer that they read everything, which is section 37.7's warning honoured by
80//! construction: a store reads every bit of the value it stores because the description says the
81//! operand is as wide as the store is, and anything the description is silent about is treated as
82//! reading the lot rather than as reading nothing.
83//!
84//! A physical register on either side. Machine IR is in SSA form until the allocator has run, so a
85//! virtual register is written once and the register a reader would be sent to instead still holds
86//! what it held. A physical one is not: the frame pointer and the stack pointer are already
87//! physical here and a call writes every register it is allowed to, so sending a reader to one of
88//! those would be sending it to whatever happened to be there.
89//!
90//! A conversion whose result is read as wide as it is written. That is a widening whose upper bits
91//! somebody does read, which is the whole instruction doing its job.
92//!
93//! A conversion whose result nothing reads at all. That is an instruction that computes something
94//! nobody wants, which is dead code rather than dead bits, and taking it out here would be this
95//! pass answering a question it was not asked and reporting a number that says it found widenings
96//! it had not. What this is about is a register something reads less of than was put in it.
97//!
98//! # How the rewrite is made
99//!
100//! One conversion at a time, as a set of changes [`crate::changes`] either takes or turns down.
101//! The set is the readers sent to the source and the conversion taken out, and those two are worth
102//! nothing apart: a reader left behind reads a register nothing writes any more. So the set is
103//! where the question is asked, and a reader this pass failed to find is a set that is refused
104//! rather than a function with a hole in it.
105//!
106//! The readers an edge holds are in the set the same way. A conversion in one block whose reader is
107//! in another is the case this pass is here for, and the argument the edge carries is how the value
108//! gets there, so sending it somewhere else is half of what taking the conversion out means.
109//!
110//! # Where it runs
111//!
112//! After selection and before allocation, which is the window where the machine instructions exist
113//! and the registers are still virtual. Section 37.6 puts it third in the group that runs there,
114//! after combining and if-conversion and before compare elimination and addressing-mode folding,
115//! and that is where `crate::pipeline` calls it.
116
117use rucc_base::Interner;
118use rucc_base::hash::Map;
119use rucc_mir as mir;
120use rucc_target::{BitInsts, Constraint, MachineInsts, Role};
121
122use crate::changes::{Changes, Reads};
123
124/// How much of a register a read that could be of any of it wants.
125///
126/// Every operand the target does not describe gets this, and no rewrite fires over a register that
127/// has it, since no instruction on any machine writes more bits than this many.
128const EVERYTHING: u32 = u32::MAX;
129
130/// Takes out every conversion whose result nothing reads above the width of its source, and gives
131/// back how many.
132///
133/// Each one that goes takes its readers with it: they are pointed at the source instead, which
134/// holds the same bits as the result did for as far as anything was looking.
135///
136/// One conversion is one set of changes, which is [`crate::changes`] asked the question this pass
137/// would otherwise be trusted about. Sending the readers of a register somewhere else and taking
138/// the instruction that wrote it out are worth nothing apart, and a reader this missed is a set
139/// the framework turns down rather than an instruction taken out from under something still
140/// reading it.
141///
142/// Run after lowering and before allocation. Running it once is enough, because the analysis is
143/// over the whole function at once and a chain of conversions is settled by the fixpoint rather
144/// than by a second run.
145pub fn dead(
146    func: &mut mir::Func,
147    insts: &BitInsts,
148    machine: &MachineInsts,
149    names: &Interner,
150) -> usize {
151    let wanted = demand(func, insts, names);
152    let mut sent: Map<mir::Reg, mir::Reg> = Map::default();
153    let mut gone: Vec<mir::Inst> = Vec::new();
154    for block in func.blocks() {
155        for inst in func.insts(block) {
156            let Some(name) = opcode(func, insts, names, inst) else { continue };
157            if !(insts.copies_low)(name) {
158                continue;
159            }
160            let Some((def, source)) = conversion(func, inst) else { continue };
161            let kept = (insts.width)(name, SOURCE).unwrap_or(EVERYTHING);
162            let read = wanted.get(def);
163            if read == 0 || read > kept {
164                continue;
165            }
166            sent.insert(def, source);
167            gone.push(inst);
168        }
169    }
170    if gone.is_empty() {
171        return 0;
172    }
173    let sent = chased(&sent);
174    let readers = Readers::of(func, &sent);
175    let mut reads = Reads::of(func);
176    let mut taken = 0;
177    for inst in gone {
178        let Some((def, _)) = conversion(func, inst) else { continue };
179        let Some(&into) = sent.get(&def) else { continue };
180        let mut set = Changes::new();
181        for &reader in readers.insts.get(&def).into_iter().flatten() {
182            // A reader that has gone is one an earlier conversion in a chain took with it, and the
183            // read it was doing went with it.
184            if func.block_of(reader).is_some() {
185                set.rename(reader, def, into);
186            }
187        }
188        for &(from, at) in readers.edges.get(&def).into_iter().flatten() {
189            let args = func[from].succs[at]
190                .args
191                .iter()
192                .map(|&arg| if arg == def { into } else { arg })
193                .collect();
194            set.carry(from, at, args);
195        }
196        set.remove(inst);
197        if set.commit(func, &mut reads, names, machine).is_ok() {
198            taken += 1;
199        }
200    }
201    taken
202}
203
204/// Everything that reads each of the registers a conversion wrote.
205///
206/// Worked out in one walk rather than per conversion, because a function with a thousand of these
207/// in it would otherwise be walked a thousand times. It is the readers as they were when the walk
208/// ran, which is enough: a rename adds a read of the register it sends a reader to, and by the time
209/// that register's own conversion is the one being taken out the reader is found from the function
210/// rather than from here.
211#[derive(Debug, Default)]
212struct Readers {
213    /// The instructions that read it, each named once however many of its operands do.
214    insts: Map<mir::Reg, Vec<mir::Inst>>,
215    /// The edges that carry it, as the block each leaves and its position in that block's list.
216    edges: Map<mir::Reg, Vec<(mir::Block, usize)>>,
217}
218
219impl Readers {
220    /// Every read of every register in the map, which is the registers the conversions wrote.
221    fn of(func: &mir::Func, sent: &Map<mir::Reg, mir::Reg>) -> Self {
222        let mut found = Self::default();
223        for block in func.blocks() {
224            for inst in func.insts(block) {
225                for operand in &func[func[inst].operands] {
226                    if operand.role != Role::Use || !sent.contains_key(&operand.reg) {
227                        continue;
228                    }
229                    let readers = found.insts.entry(operand.reg).or_default();
230                    if !readers.contains(&inst) {
231                        readers.push(inst);
232                    }
233                }
234            }
235            for (at, call) in func[block].succs.iter().enumerate() {
236                for arg in &call.args {
237                    if !sent.contains_key(arg) {
238                        continue;
239                    }
240                    let edges = found.edges.entry(*arg).or_default();
241                    if !edges.contains(&(block, at)) {
242                        edges.push((block, at));
243                    }
244                }
245            }
246        }
247        found
248    }
249}
250
251/// Where a conversion holds the register it reads.
252///
253/// A conversion is one definition and one use in that order, which is what [`conversion`] checks
254/// rather than assumes, so the source is at one.
255const SOURCE: u8 = 1;
256
257/// How many low bits of each register something reads.
258///
259/// Absent means none, which is a register nothing has been seen to read. That is the right
260/// starting point rather than a wrong one to be corrected later: the answer only grows, so a
261/// register still absent when the walk settles is one nothing reads at all.
262fn demand(func: &mir::Func, insts: &BitInsts, names: &Interner) -> Wanted {
263    // What an instruction asks of its operands is the same every round, and only how much of a
264    // conversion's result is read moves, so the target's description is asked once here rather
265    // than for every operand on every round. Per instruction, the result a conversion passes its
266    // demand through and where its reads end in `reads`. Per block, where its instructions end.
267    let mut steps: Vec<(Option<mir::Reg>, usize)> = Vec::new();
268    let mut reads: Vec<(mir::Reg, u32)> = Vec::new();
269    let mut ends: Vec<usize> = Vec::new();
270    for block in func.blocks() {
271        for inst in func.insts(block) {
272            let name = opcode(func, insts, names, inst);
273            // A conversion puts the low bits of its source in its result and nothing else, so the
274            // bits of the source above however much of the result is read are bits it takes
275            // nowhere. Anything else reads its operand at the width it names it at.
276            let copies = name.is_some_and(|name| (insts.copies_low)(name));
277            let through = conversion(func, inst).filter(|_| copies).map(|(def, _)| def);
278            let operands = &func[func[inst].operands];
279            for (at, operand) in operands.iter().enumerate() {
280                if operand.role != Role::Use {
281                    continue;
282                }
283                let Ok(at) = u8::try_from(at) else { continue };
284                reads.push((operand.reg, read(name, insts, operands, at)));
285            }
286            steps.push((through, reads.len()));
287        }
288        ends.push(steps.len());
289    }
290
291    let mut wanted = Wanted { virtuals: vec![0; func.vregs()], physical: Map::default() };
292    loop {
293        let mut moved = false;
294        let (mut step, mut from) = (0, 0);
295        for (block, &end) in func.blocks().zip(&ends) {
296            for &(through, to) in &steps[step..end] {
297                let through = through.map_or(EVERYTHING, |def| wanted.get(def));
298                for &(reg, bits) in &reads[from..to] {
299                    moved |= wanted.raise(reg, bits.min(through));
300                }
301                from = to;
302            }
303            step = end;
304            for call in &func[block].succs {
305                for (arg, param) in call.args.iter().zip(&func[call.block].params) {
306                    let asked = wanted.get(param.reg);
307                    moved |= wanted.raise(*arg, asked);
308                }
309            }
310        }
311        if !moved {
312            return wanted;
313        }
314    }
315}
316
317/// How many low bits of each register something reads, as [`demand`] works it out.
318///
319/// A virtual register's answer is in a list by its number rather than in a map, since the numbers
320/// run from nought with no gaps and the walk asks about every operand of the function on every
321/// round until nothing moves. The few physical registers go in the map.
322#[derive(Debug)]
323struct Wanted {
324    virtuals: Vec<u32>,
325    physical: Map<mir::Reg, u32>,
326}
327
328impl Wanted {
329    /// How many bits of it are read, which is none for a register nothing has been seen to read.
330    fn get(&self, reg: mir::Reg) -> u32 {
331        match reg.number() {
332            Some(number) => self.virtuals.get(number as usize).copied().unwrap_or(0),
333            None => self.physical.get(&reg).copied().unwrap_or(0),
334        }
335    }
336
337    /// Raises how much of a register is read, and says whether that changed anything.
338    fn raise(&mut self, reg: mir::Reg, bits: u32) -> bool {
339        let had = match reg.number() {
340            Some(number) => {
341                let number = number as usize;
342                if number >= self.virtuals.len() {
343                    self.virtuals.resize(number + 1, 0);
344                }
345                &mut self.virtuals[number]
346            }
347            None => self.physical.entry(reg).or_insert(0),
348        };
349        if *had >= bits {
350            return false;
351        }
352        *had = bits;
353        true
354    }
355}
356
357/// How many bits of the operand at that index the instruction reads.
358///
359/// The target's description is asked first and is the answer whenever it has one. Where it has
360/// none the operand may still be a tied one, which is the operand an instruction of this shape
361/// reads and writes in the one place: the machine writes it once and the assembly names it once,
362/// so the description names the definition and says nothing about the use beside it. Those two
363/// are the same register at the same width by the time the allocator has finished, so the width
364/// of the definition is the width of the use.
365///
366/// Anything left over reads everything, which is what keeps an address register, an opcode written
367/// as no instruction and an opcode from another target from being believed to read nothing.
368fn read(name: Option<&str>, insts: &BitInsts, operands: &[mir::Operand], at: u8) -> u32 {
369    let Some(name) = name else { return EVERYTHING };
370    if let Some(bits) = (insts.width)(name, at) {
371        return bits;
372    }
373    for (index, operand) in operands.iter().enumerate() {
374        if operand.role == Role::Use || operand.constraint != Constraint::Reuse(at) {
375            continue;
376        }
377        let Ok(index) = u8::try_from(index) else { continue };
378        return (insts.width)(name, index).unwrap_or(EVERYTHING);
379    }
380    EVERYTHING
381}
382
383/// The name this target knows an instruction by, for an instruction that is one of this target's.
384///
385/// The opcode in machine IR carries the target's prefix, because a function in the middle of being
386/// compiled holds instructions of one machine and the prefix is what says which. Anything without
387/// it is not something this description covers, and the rest of the pass treats that as knowing
388/// nothing rather than as knowing it is safe.
389fn opcode<'a>(
390    func: &mir::Func,
391    insts: &BitInsts,
392    names: &'a Interner,
393    inst: mir::Inst,
394) -> Option<&'a str> {
395    names.resolve(func[inst].opcode.name()).strip_prefix(insts.prefix)
396}
397
398/// The register a conversion writes and the register it reads, when it is one this may take out.
399///
400/// One definition and one use, both of them virtual, and no memory operand. The shape is checked
401/// rather than taken on trust from the opcode, since what the rewrite does is send every reader of
402/// the first register to the second and that is only the same program when there is exactly one of
403/// each.
404fn conversion(func: &mir::Func, inst: mir::Inst) -> Option<(mir::Reg, mir::Reg)> {
405    if func[inst].mem.is_some() {
406        return None;
407    }
408    let operands = &func[func[inst].operands];
409    let [def, source] = operands else { return None };
410    if def.role == Role::Use || source.role != Role::Use {
411        return None;
412    }
413    if !def.reg.is_virtual() || !source.reg.is_virtual() {
414        return None;
415    }
416    Some((def.reg, source.reg))
417}
418
419/// The same map with every chain in it followed to its end.
420///
421/// A chain is two conversions where the outer one reads what the inner one wrote, and both of them
422/// going means a reader of the outer one belongs to the inner one's source rather than to the
423/// inner one. The walk ends because machine IR is in SSA form here and every step goes to a
424/// register written earlier in the function, and the bound is there so that a map built any other
425/// way stops as well.
426fn chased(sent: &Map<mir::Reg, mir::Reg>) -> Map<mir::Reg, mir::Reg> {
427    sent.iter()
428        .map(|(&from, &first)| {
429            let mut into = first;
430            for _ in 0..sent.len() {
431                match sent.get(&into) {
432                    Some(&next) => into = next,
433                    None => break,
434                }
435            }
436            (from, into)
437        })
438        .collect()
439}
440
441#[cfg(test)]
442mod tests {
443    use rucc_target::x86_64::{BITS, GPR, MACHINE, RDI};
444
445    use super::*;
446
447    /// A function with one block, and the names it was built with.
448    fn empty() -> (Interner, mir::Func, mir::Block) {
449        let mut names = Interner::new();
450        let mut func = mir::Func::new(names.intern("f"));
451        let block = func.create_block();
452        (names, func, block)
453    }
454
455    /// The opcode of that name on this target.
456    fn op(names: &mut Interner, name: &str) -> mir::Opcode {
457        mir::Opcode::new(names.intern(&format!("{}{name}", BITS.prefix)))
458    }
459
460    /// The pass, over the machine this crate has a backend for.
461    fn takes(func: &mut mir::Func, names: &Interner) -> usize {
462        dead(func, &BITS, &MACHINE, names)
463    }
464
465    /// What every instruction in a block came to, as opcodes.
466    fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<String> {
467        func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
468    }
469
470    /// The registers one instruction reads, in the order its operands hold them.
471    fn reads(func: &mir::Func, inst: mir::Inst) -> Vec<mir::Reg> {
472        func[func[inst].operands]
473            .iter()
474            .filter(|operand| operand.role == Role::Use)
475            .map(|operand| operand.reg)
476            .collect()
477    }
478
479    /// The shape the whole pass is about, and the one the corpus is full of: a byte widened to a
480    /// word because C says to, and then the word written back out as a byte. The twenty four bits
481    /// in between are worked out and read by nobody.
482    #[test]
483    fn a_widening_whose_only_reader_is_as_narrow_as_its_source_goes() {
484        let (mut names, mut func, block) = empty();
485        let byte = func.new_vreg(GPR);
486        let wide = func.new_vreg(GPR);
487        let address = func.new_vreg(GPR);
488        let widen = op(&mut names, "movzx_8_32");
489        let store = op(&mut names, "mov_mr_8");
490        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
491        func.build(block, store)
492            .uses(wide, GPR)
493            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
494            .finish();
495
496        assert_eq!(takes(&mut func, &names), 1);
497
498        let left = shape(&func, &names, block);
499        assert_eq!(left.len(), 1, "the widening is still there: {left:?}");
500        let inst = func.insts(block).next().expect("the store is still there");
501        assert_eq!(reads(&func, inst)[0], byte, "the store was not sent to the source");
502    }
503
504    /// The same widening with a reader that reads the whole of what it wrote. Those upper bits are
505    /// read, so the instruction that worked them out is one doing its job.
506    #[test]
507    fn a_widening_something_reads_the_whole_of_stays() {
508        let (mut names, mut func, block) = empty();
509        let byte = func.new_vreg(GPR);
510        let wide = func.new_vreg(GPR);
511        let out = func.new_vreg(GPR);
512        let widen = op(&mut names, "movzx_8_32");
513        let copy = op(&mut names, "mov_rr_64");
514        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
515        func.build(block, copy).def(out, GPR).uses(wide, GPR).finish();
516
517        assert_eq!(takes(&mut func, &names), 0);
518        assert_eq!(shape(&func, &names, block).len(), 2);
519    }
520
521    /// The case no rewrite rule can reach, which is the reason this pass is here at all. The
522    /// widening is in one block and the only thing that reads it is in another, so the two are
523    /// never operands of one term and no pattern three levels deep sees them both.
524    #[test]
525    fn a_widening_whose_narrow_reader_is_in_another_block_goes_too() {
526        let (mut names, mut func, block) = empty();
527        let next = func.create_block();
528        let byte = func.new_vreg(GPR);
529        let wide = func.new_vreg(GPR);
530        let arrived = func.new_vreg(GPR);
531        let address = func.new_vreg(GPR);
532        let widen = op(&mut names, "movzx_8_32");
533        let store = op(&mut names, "mov_mr_8");
534        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
535        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
536        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![wide])];
537        func.build(next, store)
538            .uses(arrived, GPR)
539            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
540            .finish();
541
542        assert_eq!(takes(&mut func, &names), 1);
543
544        assert!(shape(&func, &names, block).is_empty(), "the widening is still there");
545        assert_eq!(func[block].succs[0].args, vec![byte], "the edge still carries the wide one");
546    }
547
548    /// And the same edge with a reader on the other side that wants the whole word, which is the
549    /// answer coming back across the boundary the other way.
550    #[test]
551    fn a_widening_whose_reader_in_another_block_is_wide_stays() {
552        let (mut names, mut func, block) = empty();
553        let next = func.create_block();
554        let byte = func.new_vreg(GPR);
555        let wide = func.new_vreg(GPR);
556        let arrived = func.new_vreg(GPR);
557        let address = func.new_vreg(GPR);
558        let widen = op(&mut names, "movzx_8_32");
559        let store = op(&mut names, "mov_mr_32");
560        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
561        func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
562        *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![wide])];
563        func.build(next, store)
564            .uses(arrived, GPR)
565            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
566            .finish();
567
568        assert_eq!(takes(&mut func, &names), 0);
569        assert_eq!(shape(&func, &names, block).len(), 1);
570        assert_eq!(func[block].succs[0].args, vec![wide]);
571    }
572
573    /// A chain, which is what a narrow value widened for one operation and narrowed for the next
574    /// comes out as. The middle conversion is what makes the analysis a fixpoint rather than one
575    /// walk: how much of it is read depends on how much of the one after it is, and that number is
576    /// still being worked out when it is asked for.
577    #[test]
578    fn a_chain_of_conversions_goes_the_whole_way_and_its_reader_goes_to_the_first_source() {
579        let (mut names, mut func, block) = empty();
580        let byte = func.new_vreg(GPR);
581        let wide = func.new_vreg(GPR);
582        let narrowed = func.new_vreg(GPR);
583        let out = func.new_vreg(GPR);
584        let address = func.new_vreg(GPR);
585        let widen = op(&mut names, "movzx_8_64");
586        let low = op(&mut names, "low_32");
587        let narrow = op(&mut names, "low_8");
588        let store = op(&mut names, "mov_mr_8");
589        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
590        func.build(block, low).def(narrowed, GPR).uses(wide, GPR).finish();
591        func.build(block, narrow).def(out, GPR).uses(narrowed, GPR).finish();
592        func.build(block, store)
593            .uses(out, GPR)
594            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
595            .finish();
596
597        assert_eq!(takes(&mut func, &names), 3);
598
599        let left = shape(&func, &names, block);
600        assert_eq!(left.len(), 1, "some of the three are still there: {left:?}");
601        let inst = func.insts(block).next().expect("the store is still there");
602        assert_eq!(reads(&func, inst)[0], byte, "the chain was not followed to its end");
603    }
604
605    /// A store of the whole word, which is section 37.7's warning: the bits go to memory and
606    /// something reads them from there, so a pass that thought a store read less than it stores
607    /// would take out a widening whose answer is in the program's output.
608    #[test]
609    fn a_store_reads_every_bit_of_what_it_stores() {
610        let (mut names, mut func, block) = empty();
611        let byte = func.new_vreg(GPR);
612        let wide = func.new_vreg(GPR);
613        let address = func.new_vreg(GPR);
614        let widen = op(&mut names, "movzx_8_64");
615        let store = op(&mut names, "mov_mr_64");
616        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
617        func.build(block, store)
618            .uses(wide, GPR)
619            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
620            .finish();
621
622        assert_eq!(takes(&mut func, &names), 0);
623        assert_eq!(shape(&func, &names, block).len(), 2);
624    }
625
626    /// A widening whose result is read as an address. The registers a memory operand is made of
627    /// are read whole and the description says nothing about their width, so the answer is that
628    /// everything is read rather than that nothing is.
629    #[test]
630    fn a_widening_read_as_an_address_stays() {
631        let (mut names, mut func, block) = empty();
632        let byte = func.new_vreg(GPR);
633        let wide = func.new_vreg(GPR);
634        let out = func.new_vreg(GPR);
635        let widen = op(&mut names, "movzx_8_64");
636        let load = op(&mut names, "mov_rm_32");
637        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
638        func.build(block, load)
639            .def(out, GPR)
640            .mem(mir::Mem::at(mir::Operand::read(wide, GPR)))
641            .finish();
642
643        assert_eq!(takes(&mut func, &names), 0);
644        assert_eq!(shape(&func, &names, block).len(), 2);
645    }
646
647    /// The operand an instruction of this shape reads and writes in the one place, which the
648    /// assembly names once and the description therefore has no separate width for. It is as wide
649    /// as the definition it is tied to, and an eight bit source is not enough for it.
650    #[test]
651    fn a_tied_operand_reads_as_much_as_the_definition_it_is_tied_to() {
652        let (mut names, mut func, block) = empty();
653        let byte = func.new_vreg(GPR);
654        let wide = func.new_vreg(GPR);
655        let other = func.new_vreg(GPR);
656        let sum = func.new_vreg(GPR);
657        let address = func.new_vreg(GPR);
658        let widen = op(&mut names, "movzx_8_32");
659        let add = op(&mut names, "add_rr_32");
660        let store = op(&mut names, "mov_mr_32");
661        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
662        func.build(block, add)
663            .operand(mir::Operand::write(sum, GPR).with(Constraint::Reuse(1)))
664            .uses(wide, GPR)
665            .uses(other, GPR)
666            .finish();
667        func.build(block, store)
668            .uses(sum, GPR)
669            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
670            .finish();
671
672        assert_eq!(takes(&mut func, &names), 0);
673        assert_eq!(shape(&func, &names, block).len(), 3);
674    }
675
676    /// A physical register as the source. Sending the readers there would send them to a register
677    /// the convention hands out and a call is free to destroy, which is not what SSA promises
678    /// about the virtual one they were reading.
679    #[test]
680    fn a_widening_of_a_physical_register_stays() {
681        let (mut names, mut func, block) = empty();
682        let arrived = mir::Reg::physical(RDI);
683        let wide = func.new_vreg(GPR);
684        let address = func.new_vreg(GPR);
685        let widen = op(&mut names, "movzx_8_32");
686        let store = op(&mut names, "mov_mr_8");
687        func.build(block, widen).def(wide, GPR).uses(arrived, GPR).finish();
688        func.build(block, store)
689            .uses(wide, GPR)
690            .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
691            .finish();
692
693        assert_eq!(takes(&mut func, &names), 0);
694        assert_eq!(shape(&func, &names, block).len(), 2);
695    }
696
697    /// An opcode from somewhere other than this target, which is what an instruction with no
698    /// prefix on it is. Nothing is known about how much of its operands it reads, and the answer
699    /// to knowing nothing is that it reads everything.
700    #[test]
701    fn an_opcode_this_target_does_not_describe_reads_everything() {
702        let (mut names, mut func, block) = empty();
703        let byte = func.new_vreg(GPR);
704        let wide = func.new_vreg(GPR);
705        let out = func.new_vreg(GPR);
706        let widen = op(&mut names, "movzx_8_32");
707        let foreign = mir::Opcode::new(names.intern("elsewhere.narrow"));
708        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
709        func.build(block, foreign).def(out, GPR).uses(wide, GPR).finish();
710
711        assert_eq!(takes(&mut func, &names), 0);
712        assert_eq!(shape(&func, &names, block).len(), 2);
713    }
714
715    /// A conversion nothing reads at all, which is dead code rather than dead bits. It is left for
716    /// whatever removes instructions whose answers nobody wants, so that the number this gives
717    /// back is the number of widenings it found and not a count of two different things.
718    #[test]
719    fn a_conversion_nothing_reads_is_left_for_the_pass_that_owns_dead_code() {
720        let (mut names, mut func, block) = empty();
721        let byte = func.new_vreg(GPR);
722        let wide = func.new_vreg(GPR);
723        let widen = op(&mut names, "movzx_8_32");
724        func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
725
726        assert_eq!(takes(&mut func, &names), 0);
727        assert_eq!(shape(&func, &names, block).len(), 1);
728    }
729}