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
304pub(crate) fn 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
526pub(crate) fn clone_into(
528 func: &mut Func,
529 into: Block,
530 inst: Inst,
531 map: &mut HashMap<Value, Value>,
532) {
533 let data = func[inst];
534 let args: Vec<Value> =
535 func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
536 let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
537 let span = func.span(inst);
538 let args = func.push_values(&args);
539 let fresh = func.create_inst(InstData { args, ..data }, &types, span);
540 func.append_inst(into, fresh);
541 for (old, new) in data.results().zip(func[fresh].results()) {
542 map.insert(old, new);
543 }
544}
545
546fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
552 let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
553 let cond = at(&func[func[term].args][0]);
554 let calls: Vec<BlockCall> = func.successors(term).collect();
555 let args: Vec<Vec<Value>> =
556 calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
557 Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
558}
559
560fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
568 let param = func.append_param(job.body, func[value].ty);
569 for block in func.blocks().collect::<Vec<_>>() {
570 let Some(term) = func.terminator(block) else { continue };
571 let carry = if block == job.header {
572 value
573 } else if block == copy {
574 arrived
575 } else {
576 param
577 };
578 for at in func.target_list(term).iter() {
579 let call = func[at];
580 if call.block != job.body {
581 continue;
582 }
583 let args = func.append_arg(call.args, carry);
584 func.set_block_call(at, BlockCall { args, ..call });
585 }
586 }
587 for decl in func.value_decls(value).collect::<Vec<u32>>() {
593 func.declare_value(param, decl);
594 }
595 let elsewhere: Vec<Start> = func
596 .value_starts(value)
597 .filter(|&start| {
598 func.start_place(start).map_or(start.block, |(block, _)| block) != job.header
599 })
600 .collect();
601 func.move_starts(value, param, &elsewhere);
602 for block in func.blocks().collect::<Vec<_>>() {
606 if block == job.header || block == copy {
607 continue;
608 }
609 for inst in func.insts(block).collect::<Vec<_>>() {
610 let swap = |had: Value| if had == value { param } else { had };
611 func.rewrite(func[inst].args, swap);
612 for at in func.target_list(inst).iter() {
613 func.rewrite(func[at].args, swap);
614 }
615 }
616 }
617}
618
619fn settle(func: &mut Func, an: &mut Analyses, copies: &[Block], stats: &mut Stats) -> bool {
637 let mut out: Vec<(Inst, BlockCall, bool)> = Vec::new();
638 {
639 let cfg = an.cfg(func);
640 let dom = an.dominators(func);
641 let mut ranges = Ranges::new(func, cfg, dom);
642 for © in copies {
643 let Some(term) = func.terminator(copy) else { continue };
644 let cond = func[func[term].args][0];
645 let Some(taken) = prune::settled(func, &mut ranges, copy, cond) else {
646 stats.missed(UNDECIDED);
647 continue;
648 };
649 let calls: Vec<BlockCall> = func.successors(term).collect();
650 out.push((term, if taken { calls[0] } else { calls[1] }, taken));
651 }
652 }
653 if out.is_empty() {
654 return false;
655 }
656 for (term, call, taken) in out {
657 simplify_cfg::jump_to(func, term, call);
658 stats.optimized(if taken { ENTERED } else { SKIPPED });
659 }
660 true
661}
662
663#[cfg(test)]
664mod tests {
665 use rucc_base::Interner;
666 use rucc_ir::{
667 Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
668 Signature, Start, Type, verify_func,
669 };
670 use rucc_target::{TargetInfo, Triple};
671
672 use super::{HeaderCopy, SIZE, SPEED};
673 use crate::canon::Canon;
674 use crate::cfg::Cfg;
675 use crate::dom::Dominators;
676 use crate::loops::Loops;
677 use crate::stats::Kind;
678 use crate::{Fuel, Pass, Stats};
679
680 fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
686 let mut an = crate::machine::fixtures::analyses();
687 Canon.run(func, &mut an, &mut Fuel::unlimited());
688 pass.run(func, &mut an, &mut Fuel::unlimited())
689 }
690
691 fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
693 let cfg = Cfg::new(func);
694 let dom = Dominators::new(&cfg);
695 let loops = Loops::new(&cfg, &dom);
696 (cfg, dom, loops)
697 }
698
699 fn sound(func: &Func, names: &mut Interner) {
705 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
706 let module = Module::new(names.intern("t.c"), &target);
707 if let Err(errors) = verify_func(&module, func, names) {
708 panic!("{errors:#?}");
709 }
710 }
711
712 fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
724 let mut names = Interner::new();
725 let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
726 let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
727 let mut func = Func::new(names.intern("f"), signature);
728 let entry = func.create_block();
729 let head = func.create_block();
730 let body = func.create_block();
731 let done = func.create_block();
732 let limit = match bound {
733 Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
734 None => func.append_param(entry, Type::int(32)),
735 };
736 let i = func.append_param(head, Type::int(32));
737 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
738 Builder::new(&mut func, entry).jump(head, &[zero]);
739 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
740 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
741 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
742 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
743 Builder::new(&mut func, body).jump(head, &[next]);
744 Builder::new(&mut func, done).ret(&[i]);
745 (func, names, vec![entry, head, body, done])
746 }
747
748 fn tests_at_the_top(func: &Func) -> bool {
750 let (cfg, dom, loops) = forest(func);
751 let _ = dom;
752 let id = loops.all().next().expect("there is a loop");
753 let header = loops.header(id);
754 cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
755 }
756
757 #[test]
758 fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
759 let (mut func, mut names, _) = counted(None);
760 assert!(tests_at_the_top(&func), "the shape this pass is for");
761
762 let stats = copied(&mut func, &SPEED);
763 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
764 assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
765 sound(&func, &mut names);
766 }
767
768 #[test]
769 fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
770 let (mut func, mut names, blocks) = counted(None);
771 let body = blocks[2];
772 assert!(func[body].params.is_empty(), "the body carries nothing to start with");
773
774 copied(&mut func, &SPEED);
775 assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
776 assert_eq!(
777 Cfg::new(&func).predecessors(body).len(),
778 2,
779 "one edge from the header and one from the copy"
780 );
781 sound(&func, &mut names);
782 }
783
784 #[test]
789 fn a_name_on_the_value_the_body_now_takes_as_a_parameter_goes_with_it() {
790 let (mut func, mut names, blocks) = counted(None);
791 let (head, body) = (blocks[1], blocks[2]);
792 let i = func[head].params[0];
793 let test = func.insts(head).next();
794 func.declare_value(i, 3);
795 func.declare_value_from(i, Start { decl: 4, block: body, after: None });
796 func.declare_value_from(i, Start { decl: 5, block: head, after: test });
797
798 copied(&mut func, &SPEED);
799 let param = func[body].params[0];
800 assert_eq!(func.value_decls(param).collect::<Vec<u32>>(), vec![3]);
801 assert_eq!(func.value_decls(i).collect::<Vec<u32>>(), vec![3], "still the header's");
802 let decls = |value| func.value_starts(value).map(|start| start.decl).collect::<Vec<u32>>();
803 assert_eq!(decls(param), vec![4], "the body's start is on the body's parameter");
804 assert_eq!(decls(i), vec![5], "and the header's stays on the header's value");
805 sound(&func, &mut names);
806 }
807
808 #[test]
809 fn an_entry_test_the_ranges_settle_is_taken_out() {
810 let (mut func, mut names, _) = counted(Some(10));
811
812 let stats = copied(&mut func, &SPEED);
813 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
814 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
815 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
816 sound(&func, &mut names);
817
818 let (cfg, _dom, loops) = forest(&func);
819 let id = loops.all().next().expect("the loop is still there");
820 let entry = func.entry().expect("there is an entry");
821 assert!(cfg.reaches(loops.header(id)), "and it is still reached");
822 assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
823 }
824
825 #[test]
826 fn a_loop_the_ranges_say_never_runs_is_removed() {
827 let (mut func, mut names, blocks) = counted(Some(0));
828
829 let stats = copied(&mut func, &SPEED);
830 assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
831 sound(&func, &mut names);
832
833 let (_cfg, _dom, loops) = forest(&func);
834 assert_eq!(loops.count(), 0, "there is no loop left");
835 assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
836 }
837
838 #[test]
839 fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
840 let (mut func, _names, _) = counted(None);
841
842 let stats = copied(&mut func, &SPEED);
843 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
844 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
845 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
846 }
847
848 #[test]
849 fn a_second_run_changes_nothing() {
850 let (mut func, mut names, _) = counted(None);
851 copied(&mut func, &SPEED);
852 let again =
853 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
854 assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
855 assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
856 sound(&func, &mut names);
857 }
858
859 #[test]
860 fn a_header_that_writes_to_memory_is_left_alone() {
861 let mut names = Interner::new();
862 let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
863 let mut func = Func::new(names.intern("f"), signature);
864 let entry = func.create_block();
865 let head = func.create_block();
866 let body = func.create_block();
867 let done = func.create_block();
868 let limit = func.append_param(entry, Type::int(32));
869 let addr = func.append_param(entry, Type::PTR);
870 let i = func.append_param(head, Type::int(32));
871 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
872 Builder::new(&mut func, entry).jump(head, &[zero]);
873 let access = MemInfo {
874 size: 4,
875 align: 4,
876 order: MemOrder::NotAtomic,
877 tbaa: None,
878 owns: 0,
879 restrict: Restrict::NONE,
880 };
881 Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
882 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
883 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
884 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
885 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
886 Builder::new(&mut func, body).jump(head, &[next]);
887 Builder::new(&mut func, done).ret(&[]);
888
889 let stats = copied(&mut func, &SPEED);
890 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
891 assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
892 assert!(tests_at_the_top(&func), "the loop is exactly as it was");
893 }
894
895 #[test]
896 fn a_header_larger_than_the_level_allows_is_left_alone() {
897 let stats = copied(&mut padded(6), &SIZE);
900 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
901 assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
902
903 let stats = copied(&mut padded(6), &SPEED);
904 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
905 }
906
907 fn padded(extra: usize) -> Func {
909 let (mut func, _names, blocks) = counted(None);
910 let head = blocks[1];
911 let term = func.terminator(head).expect("the header branches");
912 for _ in 0..extra {
913 let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
914 let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
915 func.remove_inst(inst);
916 func.insert_before(inst, term);
917 }
918 func
919 }
920
921 #[test]
922 fn fuel_stops_the_copy_where_it_stands() {
923 let (mut func, _names, _) = counted(None);
924 let mut an = crate::machine::fixtures::analyses();
925 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
926
927 let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
928 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
929 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
930 assert!(tests_at_the_top(&func), "and the loop is as it was");
931 }
932
933 #[test]
934 fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
935 let (mut func, _names, _) = counted(None);
940
941 let stats =
942 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
943 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
944 assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
945 assert!(tests_at_the_top(&func), "and the loop is as it was");
946 }
947
948 #[test]
949 fn a_loop_with_no_preheader_is_declined() {
950 let mut names = Interner::new();
951 let signature = Signature::new().with_params(&[Type::int(1), 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 two = func.create_block();
956 let head = func.create_block();
957 let body = func.create_block();
958 let done = func.create_block();
959 let c = func.append_param(entry, Type::int(1));
960 let limit = func.append_param(entry, Type::int(32));
961 let i = func.append_param(head, Type::int(32));
962 Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
963 let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
964 Builder::new(&mut func, one).jump(head, &[zero]);
965 let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
966 Builder::new(&mut func, two).jump(head, &[start]);
967 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
968 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
969 Builder::new(&mut func, body).jump(head, &[i]);
970 Builder::new(&mut func, done).ret(&[]);
971
972 let stats =
973 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
974 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
975 assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
976
977 let stats = copied(&mut func, &SPEED);
979 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
980 sound(&func, &mut names);
981 }
982
983 fn side_by_side() -> (Func, Interner, Vec<Block>) {
995 let mut names = Interner::new();
996 let signature = Signature::new().with_params(&[Type::int(32)]);
997 let mut func = Func::new(names.intern("f"), signature);
998 let entry = func.create_block();
999 let one = func.create_block();
1000 let up = func.create_block();
1001 let mid = func.create_block();
1002 let two = func.create_block();
1003 let down = func.create_block();
1004 let done = func.create_block();
1005 let n = func.append_param(entry, Type::int(32));
1006 let i = func.append_param(one, Type::int(32));
1007 let j = func.append_param(two, Type::int(32));
1008 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1009 Builder::new(&mut func, entry).jump(one, &[zero]);
1010 let t = Builder::new(&mut func, one).icmp(IntPred::Slt, i, n);
1011 Builder::new(&mut func, one).br_if(t, up, &[], mid, &[]);
1012 let step = Builder::new(&mut func, up).iconst(Type::int(32), 1);
1013 let next = Builder::new(&mut func, up).binary(Opcode::Add, i, step, Flags::NONE);
1014 Builder::new(&mut func, up).jump(one, &[next]);
1015 let start = Builder::new(&mut func, mid).iconst(Type::int(32), 0);
1016 Builder::new(&mut func, mid).jump(two, &[start]);
1017 let u = Builder::new(&mut func, two).icmp(IntPred::Slt, j, n);
1018 Builder::new(&mut func, two).br_if(u, down, &[], done, &[]);
1019 let stride = Builder::new(&mut func, down).iconst(Type::int(32), 1);
1020 let after = Builder::new(&mut func, down).binary(Opcode::Add, j, stride, Flags::NONE);
1021 Builder::new(&mut func, down).jump(two, &[after]);
1022 Builder::new(&mut func, done).ret(&[]);
1023 (func, names, vec![up, down])
1024 }
1025
1026 #[test]
1027 fn two_loops_that_do_not_meet_are_both_copied() {
1028 let (mut func, mut names, bodies) = side_by_side();
1029
1030 let stats = copied(&mut func, &SPEED);
1031 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1032 sound(&func, &mut names);
1033
1034 let (_cfg, _dom, loops) = forest(&func);
1035 assert_eq!(loops.count(), 2, "both loops are still loops");
1036 for id in loops.all() {
1037 let header = loops.header(id);
1038 assert!(
1039 !Cfg::new(&func).successors(header).iter().any(|&to| !loops.contains(id, to)),
1040 "and neither of them tests at the top any more"
1041 );
1042 }
1043 for body in bodies {
1044 assert_eq!(func[body].params.len(), 1, "each body carries its own counter");
1045 }
1046 }
1047
1048 fn nested() -> (Func, Interner) {
1060 let mut names = Interner::new();
1061 let signature = Signature::new().with_params(&[Type::int(32)]);
1062 let mut func = Func::new(names.intern("f"), signature);
1063 let entry = func.create_block();
1064 let outer = func.create_block();
1065 let ahead = func.create_block();
1066 let inner = func.create_block();
1067 let under = func.create_block();
1068 let latch = func.create_block();
1069 let done = func.create_block();
1070 let n = func.append_param(entry, Type::int(32));
1071 let i = func.append_param(outer, Type::int(32));
1072 let j = func.append_param(inner, Type::int(32));
1073 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1074 Builder::new(&mut func, entry).jump(outer, &[zero]);
1075 let t = Builder::new(&mut func, outer).icmp(IntPred::Slt, i, n);
1076 Builder::new(&mut func, outer).br_if(t, ahead, &[], done, &[]);
1077 let start = Builder::new(&mut func, ahead).iconst(Type::int(32), 0);
1078 Builder::new(&mut func, ahead).jump(inner, &[start]);
1079 let u = Builder::new(&mut func, inner).icmp(IntPred::Slt, j, n);
1080 Builder::new(&mut func, inner).br_if(u, under, &[], latch, &[]);
1081 let stride = Builder::new(&mut func, under).iconst(Type::int(32), 1);
1082 let after = Builder::new(&mut func, under).binary(Opcode::Add, j, stride, Flags::NONE);
1083 Builder::new(&mut func, under).jump(inner, &[after]);
1084 let step = Builder::new(&mut func, latch).iconst(Type::int(32), 1);
1085 let next = Builder::new(&mut func, latch).binary(Opcode::Add, i, step, Flags::NONE);
1086 Builder::new(&mut func, latch).jump(outer, &[next]);
1087 Builder::new(&mut func, done).ret(&[]);
1088 (func, names)
1089 }
1090
1091 #[test]
1092 fn a_loop_and_the_loop_inside_it_are_copied_one_round_apart() {
1093 let (mut func, mut names) = nested();
1098
1099 let stats = copied(&mut func, &SPEED);
1100 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1101 sound(&func, &mut names);
1102
1103 let (cfg, _dom, loops) = forest(&func);
1104 assert_eq!(loops.count(), 2, "both loops survived the copy");
1105 for id in loops.all() {
1106 let header = loops.header(id);
1107 assert!(
1108 !cfg.successors(header).iter().any(|&to| !loops.contains(id, to)),
1109 "and both test at the bottom now"
1110 );
1111 }
1112 }
1113
1114 #[test]
1115 fn a_round_stops_where_the_fuel_does() {
1116 let (mut func, mut names) = {
1120 let (mut func, names, _) = side_by_side();
1121 let mut an = crate::machine::fixtures::analyses();
1122 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
1123 (func, names)
1124 };
1125
1126 let stats =
1127 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1128 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
1129 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1130 sound(&func, &mut names);
1131 }
1132}