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