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//! # Where it runs
99//!
100//! After selection and before allocation, which is the window where the machine instructions exist
101//! and the registers are still virtual. Section 37.6 puts it third in the group that runs there,
102//! after combining and if-conversion and before compare elimination and addressing-mode folding,
103//! and that is where `crate::pipeline` calls it.
104
105use std::collections::HashMap;
106
107use rucc_base::Interner;
108use rucc_mir as mir;
109use rucc_target::{BitInsts, Constraint, Role};
110
111/// How much of a register a read that could be of any of it wants.
112///
113/// Every operand the target does not describe gets this, and no rewrite fires over a register that
114/// has it, since no instruction on any machine writes more bits than this many.
115const EVERYTHING: u32 = u32::MAX;
116
117/// Takes out every conversion whose result nothing reads above the width of its source, and gives
118/// back how many.
119///
120/// Each one that goes takes its readers with it: they are pointed at the source instead, which
121/// holds the same bits as the result did for as far as anything was looking.
122///
123/// Run after lowering and before allocation. Running it once is enough, because the analysis is
124/// over the whole function at once and a chain of conversions is settled by the fixpoint rather
125/// than by a second run.
126pub fn dead(func: &mut mir::Func, insts: &BitInsts, names: &Interner) -> usize {
127 let wanted = demand(func, insts, names);
128 let mut sent: HashMap<mir::Reg, mir::Reg> = HashMap::new();
129 let mut gone: Vec<mir::Inst> = Vec::new();
130 for block in func.blocks() {
131 for inst in func.insts(block) {
132 let Some(name) = opcode(func, insts, names, inst) else { continue };
133 if !(insts.copies_low)(name) {
134 continue;
135 }
136 let Some((def, source)) = conversion(func, inst) else { continue };
137 let kept = (insts.width)(name, SOURCE).unwrap_or(EVERYTHING);
138 let read = wanted.get(&def).copied().unwrap_or(0);
139 if read == 0 || read > kept {
140 continue;
141 }
142 sent.insert(def, source);
143 gone.push(inst);
144 }
145 }
146 if gone.is_empty() {
147 return 0;
148 }
149 rename(func, &chased(&sent));
150 for &inst in &gone {
151 func.remove_inst(inst);
152 }
153 gone.len()
154}
155
156/// Where a conversion holds the register it reads.
157///
158/// A conversion is one definition and one use in that order, which is what [`conversion`] checks
159/// rather than assumes, so the source is at one.
160const SOURCE: u8 = 1;
161
162/// How many low bits of each register something reads.
163///
164/// Absent means none, which is a register nothing has been seen to read. That is the right
165/// starting point rather than a wrong one to be corrected later: the answer only grows, so a
166/// register still absent when the walk settles is one nothing reads at all.
167fn demand(func: &mir::Func, insts: &BitInsts, names: &Interner) -> HashMap<mir::Reg, u32> {
168 let mut wanted: HashMap<mir::Reg, u32> = HashMap::new();
169 loop {
170 let mut moved = false;
171 for block in func.blocks() {
172 for inst in func.insts(block) {
173 let name = opcode(func, insts, names, inst);
174 // A conversion puts the low bits of its source in its result and nothing else, so
175 // the bits of the source above however much of the result is read are bits it
176 // takes nowhere. Anything else reads its operand at the width it names it at.
177 let copies = name.is_some_and(|name| (insts.copies_low)(name));
178 let through = conversion(func, inst)
179 .filter(|_| copies)
180 .map_or(EVERYTHING, |(def, _)| wanted.get(&def).copied().unwrap_or(0));
181 let operands = &func[func[inst].operands];
182 for (at, operand) in operands.iter().enumerate() {
183 if operand.role != Role::Use {
184 continue;
185 }
186 let Ok(at) = u8::try_from(at) else { continue };
187 let asked = read(name, insts, operands, at).min(through);
188 moved |= raise(&mut wanted, operand.reg, asked);
189 }
190 }
191 for call in &func[block].succs {
192 for (arg, param) in call.args.iter().zip(&func[call.block].params) {
193 let asked = wanted.get(¶m.reg).copied().unwrap_or(0);
194 moved |= raise(&mut wanted, *arg, asked);
195 }
196 }
197 }
198 if !moved {
199 return wanted;
200 }
201 }
202}
203
204/// Raises how much of a register is read, and says whether that changed anything.
205fn raise(wanted: &mut HashMap<mir::Reg, u32>, reg: mir::Reg, bits: u32) -> bool {
206 let had = wanted.entry(reg).or_insert(0);
207 if *had >= bits {
208 return false;
209 }
210 *had = bits;
211 true
212}
213
214/// How many bits of the operand at that index the instruction reads.
215///
216/// The target's description is asked first and is the answer whenever it has one. Where it has
217/// none the operand may still be a tied one, which is the operand an instruction of this shape
218/// reads and writes in the one place: the machine writes it once and the assembly names it once,
219/// so the description names the definition and says nothing about the use beside it. Those two
220/// are the same register at the same width by the time the allocator has finished, so the width
221/// of the definition is the width of the use.
222///
223/// Anything left over reads everything, which is what keeps an address register, an opcode written
224/// as no instruction and an opcode from another target from being believed to read nothing.
225fn read(name: Option<&str>, insts: &BitInsts, operands: &[mir::Operand], at: u8) -> u32 {
226 let Some(name) = name else { return EVERYTHING };
227 if let Some(bits) = (insts.width)(name, at) {
228 return bits;
229 }
230 for (index, operand) in operands.iter().enumerate() {
231 if operand.role == Role::Use || operand.constraint != Constraint::Reuse(at) {
232 continue;
233 }
234 let Ok(index) = u8::try_from(index) else { continue };
235 return (insts.width)(name, index).unwrap_or(EVERYTHING);
236 }
237 EVERYTHING
238}
239
240/// The name this target knows an instruction by, for an instruction that is one of this target's.
241///
242/// The opcode in machine IR carries the target's prefix, because a function in the middle of being
243/// compiled holds instructions of one machine and the prefix is what says which. Anything without
244/// it is not something this description covers, and the rest of the pass treats that as knowing
245/// nothing rather than as knowing it is safe.
246fn opcode<'a>(
247 func: &mir::Func,
248 insts: &BitInsts,
249 names: &'a Interner,
250 inst: mir::Inst,
251) -> Option<&'a str> {
252 names.resolve(func[inst].opcode.name()).strip_prefix(insts.prefix)
253}
254
255/// The register a conversion writes and the register it reads, when it is one this may take out.
256///
257/// One definition and one use, both of them virtual, and no memory operand. The shape is checked
258/// rather than taken on trust from the opcode, since what the rewrite does is send every reader of
259/// the first register to the second and that is only the same program when there is exactly one of
260/// each.
261fn conversion(func: &mir::Func, inst: mir::Inst) -> Option<(mir::Reg, mir::Reg)> {
262 if func[inst].mem.is_some() {
263 return None;
264 }
265 let operands = &func[func[inst].operands];
266 let [def, source] = operands else { return None };
267 if def.role == Role::Use || source.role != Role::Use {
268 return None;
269 }
270 if !def.reg.is_virtual() || !source.reg.is_virtual() {
271 return None;
272 }
273 Some((def.reg, source.reg))
274}
275
276/// The same map with every chain in it followed to its end.
277///
278/// A chain is two conversions where the outer one reads what the inner one wrote, and both of them
279/// going means a reader of the outer one belongs to the inner one's source rather than to the
280/// inner one. The walk ends because machine IR is in SSA form here and every step goes to a
281/// register written earlier in the function, and the bound is there so that a map built any other
282/// way stops as well.
283fn chased(sent: &HashMap<mir::Reg, mir::Reg>) -> HashMap<mir::Reg, mir::Reg> {
284 sent.iter()
285 .map(|(&from, &first)| {
286 let mut into = first;
287 for _ in 0..sent.len() {
288 match sent.get(&into) {
289 Some(&next) => into = next,
290 None => break,
291 }
292 }
293 (from, into)
294 })
295 .collect()
296}
297
298/// Sends every read of a register that is going to whatever holds the bits it held.
299///
300/// The arguments an edge carries are reads like any other and are not in any operand vector, which
301/// is the one place this is easy to get wrong.
302fn rename(func: &mut mir::Func, sent: &HashMap<mir::Reg, mir::Reg>) {
303 for block in func.blocks().collect::<Vec<_>>() {
304 for inst in func.insts(block).collect::<Vec<_>>() {
305 let operands = func[inst].operands;
306 for operand in &mut func[operands] {
307 if operand.role != Role::Use {
308 continue;
309 }
310 if let Some(&into) = sent.get(&operand.reg) {
311 operand.reg = into;
312 }
313 }
314 }
315 for call in func.succs_mut(block) {
316 for arg in &mut call.args {
317 if let Some(&into) = sent.get(arg) {
318 *arg = into;
319 }
320 }
321 }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use rucc_target::x86_64::{BITS, GPR, RDI};
328
329 use super::*;
330
331 /// A function with one block, and the names it was built with.
332 fn empty() -> (Interner, mir::Func, mir::Block) {
333 let mut names = Interner::new();
334 let mut func = mir::Func::new(names.intern("f"));
335 let block = func.create_block();
336 (names, func, block)
337 }
338
339 /// The opcode of that name on this target.
340 fn op(names: &mut Interner, name: &str) -> mir::Opcode {
341 mir::Opcode::new(names.intern(&format!("{}{name}", BITS.prefix)))
342 }
343
344 /// The pass, over the machine this crate has a backend for.
345 fn takes(func: &mut mir::Func, names: &Interner) -> usize {
346 dead(func, &BITS, names)
347 }
348
349 /// What every instruction in a block came to, as opcodes.
350 fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<String> {
351 func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
352 }
353
354 /// The registers one instruction reads, in the order its operands hold them.
355 fn reads(func: &mir::Func, inst: mir::Inst) -> Vec<mir::Reg> {
356 func[func[inst].operands]
357 .iter()
358 .filter(|operand| operand.role == Role::Use)
359 .map(|operand| operand.reg)
360 .collect()
361 }
362
363 /// The shape the whole pass is about, and the one the corpus is full of: a byte widened to a
364 /// word because C says to, and then the word written back out as a byte. The twenty four bits
365 /// in between are worked out and read by nobody.
366 #[test]
367 fn a_widening_whose_only_reader_is_as_narrow_as_its_source_goes() {
368 let (mut names, mut func, block) = empty();
369 let byte = func.new_vreg(GPR);
370 let wide = func.new_vreg(GPR);
371 let address = func.new_vreg(GPR);
372 let widen = op(&mut names, "movzx_8_32");
373 let store = op(&mut names, "mov_mr_8");
374 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
375 func.build(block, store)
376 .uses(wide, GPR)
377 .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
378 .finish();
379
380 assert_eq!(takes(&mut func, &names), 1);
381
382 let left = shape(&func, &names, block);
383 assert_eq!(left.len(), 1, "the widening is still there: {left:?}");
384 let inst = func.insts(block).next().expect("the store is still there");
385 assert_eq!(reads(&func, inst)[0], byte, "the store was not sent to the source");
386 }
387
388 /// The same widening with a reader that reads the whole of what it wrote. Those upper bits are
389 /// read, so the instruction that worked them out is one doing its job.
390 #[test]
391 fn a_widening_something_reads_the_whole_of_stays() {
392 let (mut names, mut func, block) = empty();
393 let byte = func.new_vreg(GPR);
394 let wide = func.new_vreg(GPR);
395 let out = func.new_vreg(GPR);
396 let widen = op(&mut names, "movzx_8_32");
397 let copy = op(&mut names, "mov_rr_64");
398 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
399 func.build(block, copy).def(out, GPR).uses(wide, GPR).finish();
400
401 assert_eq!(takes(&mut func, &names), 0);
402 assert_eq!(shape(&func, &names, block).len(), 2);
403 }
404
405 /// The case no rewrite rule can reach, which is the reason this pass is here at all. The
406 /// widening is in one block and the only thing that reads it is in another, so the two are
407 /// never operands of one term and no pattern three levels deep sees them both.
408 #[test]
409 fn a_widening_whose_narrow_reader_is_in_another_block_goes_too() {
410 let (mut names, mut func, block) = empty();
411 let next = func.create_block();
412 let byte = func.new_vreg(GPR);
413 let wide = func.new_vreg(GPR);
414 let arrived = func.new_vreg(GPR);
415 let address = func.new_vreg(GPR);
416 let widen = op(&mut names, "movzx_8_32");
417 let store = op(&mut names, "mov_mr_8");
418 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
419 func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
420 *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![wide])];
421 func.build(next, store)
422 .uses(arrived, GPR)
423 .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
424 .finish();
425
426 assert_eq!(takes(&mut func, &names), 1);
427
428 assert!(shape(&func, &names, block).is_empty(), "the widening is still there");
429 assert_eq!(func[block].succs[0].args, vec![byte], "the edge still carries the wide one");
430 }
431
432 /// And the same edge with a reader on the other side that wants the whole word, which is the
433 /// answer coming back across the boundary the other way.
434 #[test]
435 fn a_widening_whose_reader_in_another_block_is_wide_stays() {
436 let (mut names, mut func, block) = empty();
437 let next = func.create_block();
438 let byte = func.new_vreg(GPR);
439 let wide = func.new_vreg(GPR);
440 let arrived = func.new_vreg(GPR);
441 let address = func.new_vreg(GPR);
442 let widen = op(&mut names, "movzx_8_32");
443 let store = op(&mut names, "mov_mr_32");
444 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
445 func.params_mut(next).push(mir::Param { reg: arrived, class: GPR });
446 *func.succs_mut(block) = vec![mir::BlockCall::with(next, vec![wide])];
447 func.build(next, store)
448 .uses(arrived, GPR)
449 .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
450 .finish();
451
452 assert_eq!(takes(&mut func, &names), 0);
453 assert_eq!(shape(&func, &names, block).len(), 1);
454 assert_eq!(func[block].succs[0].args, vec![wide]);
455 }
456
457 /// A chain, which is what a narrow value widened for one operation and narrowed for the next
458 /// comes out as. The middle conversion is what makes the analysis a fixpoint rather than one
459 /// walk: how much of it is read depends on how much of the one after it is, and that number is
460 /// still being worked out when it is asked for.
461 #[test]
462 fn a_chain_of_conversions_goes_the_whole_way_and_its_reader_goes_to_the_first_source() {
463 let (mut names, mut func, block) = empty();
464 let byte = func.new_vreg(GPR);
465 let wide = func.new_vreg(GPR);
466 let narrowed = func.new_vreg(GPR);
467 let out = func.new_vreg(GPR);
468 let address = func.new_vreg(GPR);
469 let widen = op(&mut names, "movzx_8_64");
470 let low = op(&mut names, "low_32");
471 let narrow = op(&mut names, "low_8");
472 let store = op(&mut names, "mov_mr_8");
473 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
474 func.build(block, low).def(narrowed, GPR).uses(wide, GPR).finish();
475 func.build(block, narrow).def(out, GPR).uses(narrowed, GPR).finish();
476 func.build(block, store)
477 .uses(out, GPR)
478 .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
479 .finish();
480
481 assert_eq!(takes(&mut func, &names), 3);
482
483 let left = shape(&func, &names, block);
484 assert_eq!(left.len(), 1, "some of the three are still there: {left:?}");
485 let inst = func.insts(block).next().expect("the store is still there");
486 assert_eq!(reads(&func, inst)[0], byte, "the chain was not followed to its end");
487 }
488
489 /// A store of the whole word, which is section 37.7's warning: the bits go to memory and
490 /// something reads them from there, so a pass that thought a store read less than it stores
491 /// would take out a widening whose answer is in the program's output.
492 #[test]
493 fn a_store_reads_every_bit_of_what_it_stores() {
494 let (mut names, mut func, block) = empty();
495 let byte = func.new_vreg(GPR);
496 let wide = func.new_vreg(GPR);
497 let address = func.new_vreg(GPR);
498 let widen = op(&mut names, "movzx_8_64");
499 let store = op(&mut names, "mov_mr_64");
500 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
501 func.build(block, store)
502 .uses(wide, GPR)
503 .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
504 .finish();
505
506 assert_eq!(takes(&mut func, &names), 0);
507 assert_eq!(shape(&func, &names, block).len(), 2);
508 }
509
510 /// A widening whose result is read as an address. The registers a memory operand is made of
511 /// are read whole and the description says nothing about their width, so the answer is that
512 /// everything is read rather than that nothing is.
513 #[test]
514 fn a_widening_read_as_an_address_stays() {
515 let (mut names, mut func, block) = empty();
516 let byte = func.new_vreg(GPR);
517 let wide = func.new_vreg(GPR);
518 let out = func.new_vreg(GPR);
519 let widen = op(&mut names, "movzx_8_64");
520 let load = op(&mut names, "mov_rm_32");
521 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
522 func.build(block, load)
523 .def(out, GPR)
524 .mem(mir::Mem::at(mir::Operand::read(wide, GPR)))
525 .finish();
526
527 assert_eq!(takes(&mut func, &names), 0);
528 assert_eq!(shape(&func, &names, block).len(), 2);
529 }
530
531 /// The operand an instruction of this shape reads and writes in the one place, which the
532 /// assembly names once and the description therefore has no separate width for. It is as wide
533 /// as the definition it is tied to, and an eight bit source is not enough for it.
534 #[test]
535 fn a_tied_operand_reads_as_much_as_the_definition_it_is_tied_to() {
536 let (mut names, mut func, block) = empty();
537 let byte = func.new_vreg(GPR);
538 let wide = func.new_vreg(GPR);
539 let other = func.new_vreg(GPR);
540 let sum = func.new_vreg(GPR);
541 let address = func.new_vreg(GPR);
542 let widen = op(&mut names, "movzx_8_32");
543 let add = op(&mut names, "add_rr_32");
544 let store = op(&mut names, "mov_mr_32");
545 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
546 func.build(block, add)
547 .operand(mir::Operand::write(sum, GPR).with(Constraint::Reuse(1)))
548 .uses(wide, GPR)
549 .uses(other, GPR)
550 .finish();
551 func.build(block, store)
552 .uses(sum, GPR)
553 .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
554 .finish();
555
556 assert_eq!(takes(&mut func, &names), 0);
557 assert_eq!(shape(&func, &names, block).len(), 3);
558 }
559
560 /// A physical register as the source. Sending the readers there would send them to a register
561 /// the convention hands out and a call is free to destroy, which is not what SSA promises
562 /// about the virtual one they were reading.
563 #[test]
564 fn a_widening_of_a_physical_register_stays() {
565 let (mut names, mut func, block) = empty();
566 let arrived = mir::Reg::physical(RDI);
567 let wide = func.new_vreg(GPR);
568 let address = func.new_vreg(GPR);
569 let widen = op(&mut names, "movzx_8_32");
570 let store = op(&mut names, "mov_mr_8");
571 func.build(block, widen).def(wide, GPR).uses(arrived, GPR).finish();
572 func.build(block, store)
573 .uses(wide, GPR)
574 .mem(mir::Mem::at(mir::Operand::read(address, GPR)))
575 .finish();
576
577 assert_eq!(takes(&mut func, &names), 0);
578 assert_eq!(shape(&func, &names, block).len(), 2);
579 }
580
581 /// An opcode from somewhere other than this target, which is what an instruction with no
582 /// prefix on it is. Nothing is known about how much of its operands it reads, and the answer
583 /// to knowing nothing is that it reads everything.
584 #[test]
585 fn an_opcode_this_target_does_not_describe_reads_everything() {
586 let (mut names, mut func, block) = empty();
587 let byte = func.new_vreg(GPR);
588 let wide = func.new_vreg(GPR);
589 let out = func.new_vreg(GPR);
590 let widen = op(&mut names, "movzx_8_32");
591 let foreign = mir::Opcode::new(names.intern("elsewhere.narrow"));
592 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
593 func.build(block, foreign).def(out, GPR).uses(wide, GPR).finish();
594
595 assert_eq!(takes(&mut func, &names), 0);
596 assert_eq!(shape(&func, &names, block).len(), 2);
597 }
598
599 /// A conversion nothing reads at all, which is dead code rather than dead bits. It is left for
600 /// whatever removes instructions whose answers nobody wants, so that the number this gives
601 /// back is the number of widenings it found and not a count of two different things.
602 #[test]
603 fn a_conversion_nothing_reads_is_left_for_the_pass_that_owns_dead_code() {
604 let (mut names, mut func, block) = empty();
605 let byte = func.new_vreg(GPR);
606 let wide = func.new_vreg(GPR);
607 let widen = op(&mut names, "movzx_8_32");
608 func.build(block, widen).def(wide, GPR).uses(byte, GPR).finish();
609
610 assert_eq!(takes(&mut func, &names), 0);
611 assert_eq!(shape(&func, &names, block).len(), 1);
612 }
613}