1use std::collections::{HashMap, HashSet};
72
73use rucc_cost::heuristics;
74use rucc_ir::{
75 Block, BlockCall, Builder, ExtraKind, Func, Inst, InstData, Opcode, Type, Value, ValueList,
76};
77
78use crate::cfg::Cfg;
79use crate::dom::Dominators;
80use crate::loops::{LoopId, Loops};
81use crate::range::query::Ranges;
82use crate::{Analyses, Fuel, Pass, Preserved, Stats, prune, simplify_cfg};
83
84const COPIED: &str = "loop header copied in front of the loop so the test is at the bottom";
85const ENTERED: &str = "entry test removed, the value ranges say the loop runs";
86const SKIPPED: &str = "loop removed, the value ranges say the entry test never holds";
87const UNDECIDED: &str = "entry test kept, the value ranges do not settle whether the loop runs";
88const ALREADY: &str = "loop left as it was, it already tests at the bottom";
89const TOO_BIG: &str = "loop header not copied, it is larger than this level allows";
90const EFFECTS: &str = "loop header not copied, something in it may not be repeated";
91const SHAPE: &str = "loop header not copied, its exit is not a two way branch";
92const ESCAPES: &str = "loop header not copied, a value it defines is read outside the loop";
93const NO_PREHEADER: &str = "loop header not copied, the loop has not been canonicalized";
94const NO_FUEL: &str = "loop left as it was, the pass ran out of fuel";
95
96#[derive(Debug)]
102pub struct HeaderCopy {
103 name: &'static str,
105 budget: u32,
108}
109
110pub static SPEED: HeaderCopy =
112 HeaderCopy { name: "header-copy", budget: heuristics::LOOP_HEADER_INSNS_FOR_SPEED };
113
114pub static SIZE: HeaderCopy =
116 HeaderCopy { name: "header-copy-small", budget: heuristics::LOOP_HEADER_INSNS_FOR_SIZE };
117
118impl Pass for HeaderCopy {
119 fn name(&self) -> &'static str {
120 self.name
121 }
122
123 fn describe(&self) -> &'static str {
124 "copies a loop header in front of the loop, turning a while into a do-while"
125 }
126
127 fn preserves(&self) -> Preserved {
128 Preserved::NONE
130 }
131
132 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
133 let mut stats = Stats::new();
134 if func.entry().is_none() {
135 return stats;
136 }
137 let mut done = HashSet::new();
138 let mut say = true;
139 let mut dry = false;
140 loop {
141 let jobs = self.plan(func, an, &done, &mut stats, say);
142 say = false;
143 if jobs.is_empty() {
144 break;
145 }
146 let mut copies = Vec::with_capacity(jobs.len());
147 for job in &jobs {
148 if !fuel.take() {
149 stats.missed(NO_FUEL);
150 dry = true;
151 break;
152 }
153 done.insert(job.header);
154 done.insert(job.body);
155 copies.push(apply(func, job));
156 stats.optimized(COPIED);
157 }
158 an.clear();
159 if settle(func, an, &copies, &mut stats) {
160 an.clear();
161 }
162 if dry {
163 break;
164 }
165 }
166 if stats.changed() {
167 simplify_cfg::sweep(func, an, &mut stats);
170 }
171 an.clear();
172 stats
173 }
174}
175
176#[derive(Debug)]
178struct Job {
179 id: LoopId,
181 header: Block,
183 entry: Block,
185 body: Block,
191 carried: Vec<Value>,
194}
195
196#[derive(Debug)]
198struct Candidate {
199 id: LoopId,
201 header: Block,
203 entry: Block,
205 body: Block,
207 defined: Vec<Value>,
210}
211
212impl HeaderCopy {
213 fn plan(
226 &self,
227 func: &Func,
228 an: &mut Analyses,
229 done: &HashSet<Block>,
230 stats: &mut Stats,
231 say: bool,
232 ) -> Vec<Job> {
233 let (cfg, dom, loops) = (an.cfg(func), an.dominators(func), an.loops(func));
234 let mut wanted = Vec::new();
235 for id in loops.all() {
236 let header = loops.header(id);
237 if done.contains(&header) {
238 continue;
239 }
240 match self.consider(func, cfg, loops, id, header) {
241 Ok(candidate) => wanted.push(candidate),
242 Err(why) if say && why == ALREADY => stats.note(ALREADY),
243 Err(why) if say => stats.missed(why),
244 Err(_) => (),
245 }
246 }
247 let jobs = carried(func, dom, loops, wanted, stats, say);
248 independent(loops, jobs)
249 }
250
251 fn consider(
257 &self,
258 func: &Func,
259 cfg: &Cfg,
260 loops: &Loops,
261 id: LoopId,
262 header: Block,
263 ) -> Result<Candidate, &'static str> {
264 let leaves = cfg.successors(header).iter().any(|&to| !loops.contains(id, to));
265 if !leaves {
266 return Err(ALREADY);
269 }
270 let entry = loops.preheader(cfg, id).ok_or(NO_PREHEADER)?;
271 let term = func.terminator(header).ok_or(SHAPE)?;
272 if func[term].opcode != Opcode::BrIf {
273 return Err(SHAPE);
274 }
275 let calls: Vec<BlockCall> = func.successors(term).collect();
276 let [then_call, else_call] = calls[..].try_into().map_err(|_| SHAPE)?;
277 let body = match (loops.contains(id, then_call.block), loops.contains(id, else_call.block))
278 {
279 (true, false) => then_call.block,
280 (false, true) => else_call.block,
281 _ => return Err(SHAPE),
282 };
283 if body == header {
284 return Err(SHAPE);
285 }
286 let insts: Vec<Inst> = func.insts(header).filter(|&inst| inst != term).collect();
287 if insts.len() > self.budget as usize {
288 return Err(TOO_BIG);
289 }
290 for &inst in &insts {
291 if !repeatable(func, inst) {
292 return Err(EFFECTS);
293 }
294 }
295 let mut defined: Vec<Value> = func[header].params.clone();
296 for &inst in &insts {
297 defined.extend(func[inst].results());
298 }
299 Ok(Candidate { id, header, entry, body, defined })
300 }
301}
302
303fn repeatable(func: &Func, inst: Inst) -> bool {
313 let data = func[inst];
314 if data.opcode.has_effects() || func.carries_mem(inst) {
315 return false;
316 }
317 matches!(
318 data.extra.kind(),
319 ExtraKind::None
320 | ExtraKind::Imm
321 | ExtraKind::Symbol
322 | ExtraKind::IntPred
323 | ExtraKind::FloatPred
324 )
325}
326
327fn carried(
342 func: &Func,
343 dom: &Dominators,
344 loops: &Loops,
345 wanted: Vec<Candidate>,
346 stats: &mut Stats,
347 say: bool,
348) -> Vec<Job> {
349 let mut watched: HashMap<Value, usize> = HashMap::new();
350 for (which, candidate) in wanted.iter().enumerate() {
351 for &value in &candidate.defined {
352 watched.insert(value, which);
353 }
354 }
355 let mut read: Vec<HashSet<Value>> = vec![HashSet::new(); wanted.len()];
356 let mut escapes = vec![false; wanted.len()];
357 let mut names: Vec<usize> = Vec::new();
358 for block in func.blocks() {
359 names.clear();
360 for inst in func.insts(block) {
361 reads(func, inst, block, &wanted, &watched, &mut read, &mut names);
362 }
363 for &which in &names {
364 let candidate = &wanted[which];
365 if !loops.contains(candidate.id, block) || !dom.dominates(candidate.body, block) {
366 escapes[which] = true;
367 }
368 }
369 }
370 let mut jobs = Vec::new();
371 for (which, candidate) in wanted.into_iter().enumerate() {
372 if escapes[which] {
373 if say {
374 stats.missed(ESCAPES);
375 }
376 continue;
377 }
378 let taken = &read[which];
379 let carried = candidate.defined.into_iter().filter(|value| taken.contains(value)).collect();
380 jobs.push(Job {
381 id: candidate.id,
382 header: candidate.header,
383 entry: candidate.entry,
384 body: candidate.body,
385 carried,
386 });
387 }
388 jobs
389}
390
391fn reads(
399 func: &Func,
400 inst: Inst,
401 block: Block,
402 wanted: &[Candidate],
403 watched: &HashMap<Value, usize>,
404 read: &mut [HashSet<Value>],
405 names: &mut Vec<usize>,
406) {
407 let mut note = |value: Value| {
408 let Some(&which) = watched.get(&value) else { return };
409 if block == wanted[which].header {
410 return;
411 }
412 read[which].insert(value);
413 if !names.contains(&which) {
414 names.push(which);
415 }
416 };
417 for &value in &func[func[inst].args] {
418 note(value);
419 }
420 for call in func.successors(inst) {
421 for &value in &func[call.args] {
422 note(value);
423 }
424 }
425}
426
427fn independent(loops: &Loops, jobs: Vec<Job>) -> Vec<Job> {
444 let mut blocked = vec![false; loops.count()];
445 let mut taken = vec![false; loops.count()];
446 let mut kept: Vec<Job> = Vec::new();
447 for job in jobs {
448 if blocked[job.id.index()] || inside(loops, &taken, job.entry) {
449 continue;
450 }
451 let mut up = Some(job.id);
452 while let Some(id) = up {
453 blocked[id.index()] = true;
454 up = loops.parent(id);
455 }
456 let mut down = vec![job.id];
457 while let Some(id) = down.pop() {
458 blocked[id.index()] = true;
459 down.extend(loops.children(id));
460 }
461 let mut around = loops.innermost(job.entry);
463 while let Some(id) = around {
464 blocked[id.index()] = true;
465 around = loops.parent(id);
466 }
467 taken[job.id.index()] = true;
468 kept.push(job);
469 }
470 kept
471}
472
473fn inside(loops: &Loops, taken: &[bool], block: Block) -> bool {
475 let mut walk = loops.innermost(block);
476 while let Some(id) = walk {
477 if taken[id.index()] {
478 return true;
479 }
480 walk = loops.parent(id);
481 }
482 false
483}
484
485fn apply(func: &mut Func, job: &Job) -> Block {
487 let term = func.terminator(job.header).expect("the plan read this terminator");
488 let entry_term = func.terminator(job.entry).expect("a preheader ends in a jump");
489 let incoming = edge_args(func, entry_term, job.header);
492 let mut map: HashMap<Value, Value> = HashMap::new();
493 for (¶m, &arg) in func[job.header].params.clone().iter().zip(&incoming) {
494 map.insert(param, arg);
495 }
496 let copy = func.create_block();
497 let insts: Vec<Inst> = func.insts(job.header).filter(|&inst| inst != term).collect();
498 for inst in insts {
499 clone_into(func, copy, inst, &mut map);
500 }
501 clone_branch(func, copy, term, &map);
502 for at in func.target_list(entry_term).iter() {
503 let call = func[at];
504 if call.block == job.header {
505 func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY, ..call });
506 }
507 }
508 for &value in &job.carried {
509 let arrived = map.get(&value).copied().unwrap_or(value);
510 merge(func, job, copy, value, arrived);
511 }
512 copy
513}
514
515fn edge_args(func: &Func, term: Inst, to: Block) -> Vec<Value> {
517 for call in func.successors(term) {
518 if call.block == to {
519 return func[call.args].to_vec();
520 }
521 }
522 Vec::new()
523}
524
525fn clone_into(func: &mut Func, into: Block, inst: Inst, map: &mut HashMap<Value, Value>) {
527 let data = func[inst];
528 let args: Vec<Value> =
529 func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
530 let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
531 let span = func.span(inst);
532 let args = func.push_values(&args);
533 let fresh = func.create_inst(InstData { args, ..data }, &types, span);
534 func.append_inst(into, fresh);
535 for (old, new) in data.results().zip(func[fresh].results()) {
536 map.insert(old, new);
537 }
538}
539
540fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
546 let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
547 let cond = at(&func[func[term].args][0]);
548 let calls: Vec<BlockCall> = func.successors(term).collect();
549 let args: Vec<Vec<Value>> =
550 calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
551 Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
552}
553
554fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
562 let param = func.append_param(job.body, func[value].ty);
563 for block in func.blocks().collect::<Vec<_>>() {
564 let Some(term) = func.terminator(block) else { continue };
565 let carry = if block == job.header {
566 value
567 } else if block == copy {
568 arrived
569 } else {
570 param
571 };
572 for at in func.target_list(term).iter() {
573 let call = func[at];
574 if call.block != job.body {
575 continue;
576 }
577 let args = func.append_arg(call.args, carry);
578 func.set_block_call(at, BlockCall { args, ..call });
579 }
580 }
581 for block in func.blocks().collect::<Vec<_>>() {
585 if block == job.header || block == copy {
586 continue;
587 }
588 for inst in func.insts(block).collect::<Vec<_>>() {
589 let swap = |had: Value| if had == value { param } else { had };
590 func.rewrite(func[inst].args, swap);
591 for at in func.target_list(inst).iter() {
592 func.rewrite(func[at].args, swap);
593 }
594 }
595 }
596}
597
598fn settle(func: &mut Func, an: &mut Analyses, copies: &[Block], stats: &mut Stats) -> bool {
616 let mut out: Vec<(Inst, BlockCall, bool)> = Vec::new();
617 {
618 let cfg = an.cfg(func);
619 let dom = an.dominators(func);
620 let mut ranges = Ranges::new(func, cfg, dom);
621 for © in copies {
622 let Some(term) = func.terminator(copy) else { continue };
623 let cond = func[func[term].args][0];
624 let Some(taken) = prune::settled(func, &mut ranges, copy, cond) else {
625 stats.missed(UNDECIDED);
626 continue;
627 };
628 let calls: Vec<BlockCall> = func.successors(term).collect();
629 out.push((term, if taken { calls[0] } else { calls[1] }, taken));
630 }
631 }
632 if out.is_empty() {
633 return false;
634 }
635 for (term, call, taken) in out {
636 simplify_cfg::jump_to(func, term, call);
637 stats.optimized(if taken { ENTERED } else { SKIPPED });
638 }
639 true
640}
641
642#[cfg(test)]
643mod tests {
644 use rucc_base::Interner;
645 use rucc_ir::{
646 Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
647 Signature, Type, verify_func,
648 };
649 use rucc_target::{TargetInfo, Triple};
650
651 use super::{HeaderCopy, SIZE, SPEED};
652 use crate::canon::Canon;
653 use crate::cfg::Cfg;
654 use crate::dom::Dominators;
655 use crate::loops::Loops;
656 use crate::stats::Kind;
657 use crate::{Fuel, Pass, Stats};
658
659 fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
665 let mut an = crate::machine::fixtures::analyses();
666 Canon.run(func, &mut an, &mut Fuel::unlimited());
667 pass.run(func, &mut an, &mut Fuel::unlimited())
668 }
669
670 fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
672 let cfg = Cfg::new(func);
673 let dom = Dominators::new(&cfg);
674 let loops = Loops::new(&cfg, &dom);
675 (cfg, dom, loops)
676 }
677
678 fn sound(func: &Func, names: &mut Interner) {
684 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
685 let module = Module::new(names.intern("t.c"), &target);
686 if let Err(errors) = verify_func(&module, func, names) {
687 panic!("{errors:#?}");
688 }
689 }
690
691 fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
703 let mut names = Interner::new();
704 let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
705 let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
706 let mut func = Func::new(names.intern("f"), signature);
707 let entry = func.create_block();
708 let head = func.create_block();
709 let body = func.create_block();
710 let done = func.create_block();
711 let limit = match bound {
712 Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
713 None => func.append_param(entry, Type::int(32)),
714 };
715 let i = func.append_param(head, Type::int(32));
716 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
717 Builder::new(&mut func, entry).jump(head, &[zero]);
718 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
719 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
720 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
721 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
722 Builder::new(&mut func, body).jump(head, &[next]);
723 Builder::new(&mut func, done).ret(&[i]);
724 (func, names, vec![entry, head, body, done])
725 }
726
727 fn tests_at_the_top(func: &Func) -> bool {
729 let (cfg, dom, loops) = forest(func);
730 let _ = dom;
731 let id = loops.all().next().expect("there is a loop");
732 let header = loops.header(id);
733 cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
734 }
735
736 #[test]
737 fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
738 let (mut func, mut names, _) = counted(None);
739 assert!(tests_at_the_top(&func), "the shape this pass is for");
740
741 let stats = copied(&mut func, &SPEED);
742 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
743 assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
744 sound(&func, &mut names);
745 }
746
747 #[test]
748 fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
749 let (mut func, mut names, blocks) = counted(None);
750 let body = blocks[2];
751 assert!(func[body].params.is_empty(), "the body carries nothing to start with");
752
753 copied(&mut func, &SPEED);
754 assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
755 assert_eq!(
756 Cfg::new(&func).predecessors(body).len(),
757 2,
758 "one edge from the header and one from the copy"
759 );
760 sound(&func, &mut names);
761 }
762
763 #[test]
764 fn an_entry_test_the_ranges_settle_is_taken_out() {
765 let (mut func, mut names, _) = counted(Some(10));
766
767 let stats = copied(&mut func, &SPEED);
768 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
769 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
770 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
771 sound(&func, &mut names);
772
773 let (cfg, _dom, loops) = forest(&func);
774 let id = loops.all().next().expect("the loop is still there");
775 let entry = func.entry().expect("there is an entry");
776 assert!(cfg.reaches(loops.header(id)), "and it is still reached");
777 assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
778 }
779
780 #[test]
781 fn a_loop_the_ranges_say_never_runs_is_removed() {
782 let (mut func, mut names, blocks) = counted(Some(0));
783
784 let stats = copied(&mut func, &SPEED);
785 assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
786 sound(&func, &mut names);
787
788 let (_cfg, _dom, loops) = forest(&func);
789 assert_eq!(loops.count(), 0, "there is no loop left");
790 assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
791 }
792
793 #[test]
794 fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
795 let (mut func, _names, _) = counted(None);
796
797 let stats = copied(&mut func, &SPEED);
798 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
799 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
800 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
801 }
802
803 #[test]
804 fn a_second_run_changes_nothing() {
805 let (mut func, mut names, _) = counted(None);
806 copied(&mut func, &SPEED);
807 let again =
808 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
809 assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
810 assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
811 sound(&func, &mut names);
812 }
813
814 #[test]
815 fn a_header_that_writes_to_memory_is_left_alone() {
816 let mut names = Interner::new();
817 let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
818 let mut func = Func::new(names.intern("f"), signature);
819 let entry = func.create_block();
820 let head = func.create_block();
821 let body = func.create_block();
822 let done = func.create_block();
823 let limit = func.append_param(entry, Type::int(32));
824 let addr = func.append_param(entry, Type::PTR);
825 let i = func.append_param(head, Type::int(32));
826 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
827 Builder::new(&mut func, entry).jump(head, &[zero]);
828 let access = MemInfo {
829 size: 4,
830 align: 4,
831 order: MemOrder::NotAtomic,
832 tbaa: None,
833 owns: 0,
834 restrict: Restrict::NONE,
835 };
836 Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
837 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
838 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
839 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
840 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
841 Builder::new(&mut func, body).jump(head, &[next]);
842 Builder::new(&mut func, done).ret(&[]);
843
844 let stats = copied(&mut func, &SPEED);
845 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
846 assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
847 assert!(tests_at_the_top(&func), "the loop is exactly as it was");
848 }
849
850 #[test]
851 fn a_header_larger_than_the_level_allows_is_left_alone() {
852 let stats = copied(&mut padded(6), &SIZE);
855 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
856 assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
857
858 let stats = copied(&mut padded(6), &SPEED);
859 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
860 }
861
862 fn padded(extra: usize) -> Func {
864 let (mut func, _names, blocks) = counted(None);
865 let head = blocks[1];
866 let term = func.terminator(head).expect("the header branches");
867 for _ in 0..extra {
868 let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
869 let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
870 func.remove_inst(inst);
871 func.insert_before(inst, term);
872 }
873 func
874 }
875
876 #[test]
877 fn fuel_stops_the_copy_where_it_stands() {
878 let (mut func, _names, _) = counted(None);
879 let mut an = crate::machine::fixtures::analyses();
880 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
881
882 let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
883 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
884 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
885 assert!(tests_at_the_top(&func), "and the loop is as it was");
886 }
887
888 #[test]
889 fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
890 let (mut func, _names, _) = counted(None);
895
896 let stats =
897 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
898 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
899 assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
900 assert!(tests_at_the_top(&func), "and the loop is as it was");
901 }
902
903 #[test]
904 fn a_loop_with_no_preheader_is_declined() {
905 let mut names = Interner::new();
906 let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
907 let mut func = Func::new(names.intern("f"), signature);
908 let entry = func.create_block();
909 let one = func.create_block();
910 let two = func.create_block();
911 let head = func.create_block();
912 let body = func.create_block();
913 let done = func.create_block();
914 let c = func.append_param(entry, Type::int(1));
915 let limit = func.append_param(entry, Type::int(32));
916 let i = func.append_param(head, Type::int(32));
917 Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
918 let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
919 Builder::new(&mut func, one).jump(head, &[zero]);
920 let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
921 Builder::new(&mut func, two).jump(head, &[start]);
922 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
923 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
924 Builder::new(&mut func, body).jump(head, &[i]);
925 Builder::new(&mut func, done).ret(&[]);
926
927 let stats =
928 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
929 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
930 assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
931
932 let stats = copied(&mut func, &SPEED);
934 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
935 sound(&func, &mut names);
936 }
937
938 fn side_by_side() -> (Func, Interner, Vec<Block>) {
950 let mut names = Interner::new();
951 let signature = Signature::new().with_params(&[Type::int(32)]);
952 let mut func = Func::new(names.intern("f"), signature);
953 let entry = func.create_block();
954 let one = func.create_block();
955 let up = func.create_block();
956 let mid = func.create_block();
957 let two = func.create_block();
958 let down = func.create_block();
959 let done = func.create_block();
960 let n = func.append_param(entry, Type::int(32));
961 let i = func.append_param(one, Type::int(32));
962 let j = func.append_param(two, Type::int(32));
963 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
964 Builder::new(&mut func, entry).jump(one, &[zero]);
965 let t = Builder::new(&mut func, one).icmp(IntPred::Slt, i, n);
966 Builder::new(&mut func, one).br_if(t, up, &[], mid, &[]);
967 let step = Builder::new(&mut func, up).iconst(Type::int(32), 1);
968 let next = Builder::new(&mut func, up).binary(Opcode::Add, i, step, Flags::NONE);
969 Builder::new(&mut func, up).jump(one, &[next]);
970 let start = Builder::new(&mut func, mid).iconst(Type::int(32), 0);
971 Builder::new(&mut func, mid).jump(two, &[start]);
972 let u = Builder::new(&mut func, two).icmp(IntPred::Slt, j, n);
973 Builder::new(&mut func, two).br_if(u, down, &[], done, &[]);
974 let stride = Builder::new(&mut func, down).iconst(Type::int(32), 1);
975 let after = Builder::new(&mut func, down).binary(Opcode::Add, j, stride, Flags::NONE);
976 Builder::new(&mut func, down).jump(two, &[after]);
977 Builder::new(&mut func, done).ret(&[]);
978 (func, names, vec![up, down])
979 }
980
981 #[test]
982 fn two_loops_that_do_not_meet_are_both_copied() {
983 let (mut func, mut names, bodies) = side_by_side();
984
985 let stats = copied(&mut func, &SPEED);
986 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
987 sound(&func, &mut names);
988
989 let (_cfg, _dom, loops) = forest(&func);
990 assert_eq!(loops.count(), 2, "both loops are still loops");
991 for id in loops.all() {
992 let header = loops.header(id);
993 assert!(
994 !Cfg::new(&func).successors(header).iter().any(|&to| !loops.contains(id, to)),
995 "and neither of them tests at the top any more"
996 );
997 }
998 for body in bodies {
999 assert_eq!(func[body].params.len(), 1, "each body carries its own counter");
1000 }
1001 }
1002
1003 fn nested() -> (Func, Interner) {
1015 let mut names = Interner::new();
1016 let signature = Signature::new().with_params(&[Type::int(32)]);
1017 let mut func = Func::new(names.intern("f"), signature);
1018 let entry = func.create_block();
1019 let outer = func.create_block();
1020 let ahead = func.create_block();
1021 let inner = func.create_block();
1022 let under = func.create_block();
1023 let latch = func.create_block();
1024 let done = func.create_block();
1025 let n = func.append_param(entry, Type::int(32));
1026 let i = func.append_param(outer, Type::int(32));
1027 let j = func.append_param(inner, Type::int(32));
1028 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1029 Builder::new(&mut func, entry).jump(outer, &[zero]);
1030 let t = Builder::new(&mut func, outer).icmp(IntPred::Slt, i, n);
1031 Builder::new(&mut func, outer).br_if(t, ahead, &[], done, &[]);
1032 let start = Builder::new(&mut func, ahead).iconst(Type::int(32), 0);
1033 Builder::new(&mut func, ahead).jump(inner, &[start]);
1034 let u = Builder::new(&mut func, inner).icmp(IntPred::Slt, j, n);
1035 Builder::new(&mut func, inner).br_if(u, under, &[], latch, &[]);
1036 let stride = Builder::new(&mut func, under).iconst(Type::int(32), 1);
1037 let after = Builder::new(&mut func, under).binary(Opcode::Add, j, stride, Flags::NONE);
1038 Builder::new(&mut func, under).jump(inner, &[after]);
1039 let step = Builder::new(&mut func, latch).iconst(Type::int(32), 1);
1040 let next = Builder::new(&mut func, latch).binary(Opcode::Add, i, step, Flags::NONE);
1041 Builder::new(&mut func, latch).jump(outer, &[next]);
1042 Builder::new(&mut func, done).ret(&[]);
1043 (func, names)
1044 }
1045
1046 #[test]
1047 fn a_loop_and_the_loop_inside_it_are_copied_one_round_apart() {
1048 let (mut func, mut names) = nested();
1053
1054 let stats = copied(&mut func, &SPEED);
1055 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1056 sound(&func, &mut names);
1057
1058 let (cfg, _dom, loops) = forest(&func);
1059 assert_eq!(loops.count(), 2, "both loops survived the copy");
1060 for id in loops.all() {
1061 let header = loops.header(id);
1062 assert!(
1063 !cfg.successors(header).iter().any(|&to| !loops.contains(id, to)),
1064 "and both test at the bottom now"
1065 );
1066 }
1067 }
1068
1069 #[test]
1070 fn a_round_stops_where_the_fuel_does() {
1071 let (mut func, mut names) = {
1075 let (mut func, names, _) = side_by_side();
1076 let mut an = crate::machine::fixtures::analyses();
1077 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
1078 (func, names)
1079 };
1080
1081 let stats =
1082 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1083 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
1084 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1085 sound(&func, &mut names);
1086 }
1087}