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 blocks: Vec<Block>,
199}
200
201#[derive(Debug)]
203struct Candidate {
204 id: LoopId,
206 header: Block,
208 entry: Block,
210 body: Block,
212 defined: Vec<Value>,
215}
216
217impl HeaderCopy {
218 fn plan(
231 &self,
232 func: &Func,
233 an: &mut Analyses,
234 done: &HashSet<Block>,
235 stats: &mut Stats,
236 say: bool,
237 ) -> Vec<Job> {
238 let (cfg, dom, loops) = (an.cfg(func), an.dominators(func), an.loops(func));
239 let mut wanted = Vec::new();
240 for id in loops.all() {
241 let header = loops.header(id);
242 if done.contains(&header) {
243 continue;
244 }
245 match self.consider(func, cfg, loops, id, header) {
246 Ok(candidate) => wanted.push(candidate),
247 Err(why) if say && why == ALREADY => stats.note(ALREADY),
248 Err(why) if say => stats.missed(why),
249 Err(_) => (),
250 }
251 }
252 let jobs = carried(func, dom, loops, wanted, stats, say);
253 let mut jobs = independent(loops, jobs);
254 for job in &mut jobs {
255 job.blocks = loops.blocks(job.id).to_vec();
256 }
257 jobs
258 }
259
260 fn consider(
266 &self,
267 func: &Func,
268 cfg: &Cfg,
269 loops: &Loops,
270 id: LoopId,
271 header: Block,
272 ) -> Result<Candidate, &'static str> {
273 let leaves = cfg.successors(header).iter().any(|&to| !loops.contains(id, to));
274 if !leaves {
275 return Err(ALREADY);
278 }
279 let entry = loops.preheader(cfg, id).ok_or(NO_PREHEADER)?;
280 let term = func.terminator(header).ok_or(SHAPE)?;
281 if func[term].opcode != Opcode::BrIf {
282 return Err(SHAPE);
283 }
284 let calls: Vec<BlockCall> = func.successors(term).collect();
285 let [then_call, else_call] = calls[..].try_into().map_err(|_| SHAPE)?;
286 let body = match (loops.contains(id, then_call.block), loops.contains(id, else_call.block))
287 {
288 (true, false) => then_call.block,
289 (false, true) => else_call.block,
290 _ => return Err(SHAPE),
291 };
292 if body == header {
293 return Err(SHAPE);
294 }
295 let insts: Vec<Inst> = func.insts(header).filter(|&inst| inst != term).collect();
296 if insts.len() > self.budget as usize {
297 return Err(TOO_BIG);
298 }
299 for &inst in &insts {
300 if !repeatable(func, inst) {
301 return Err(EFFECTS);
302 }
303 }
304 let mut defined: Vec<Value> = func[header].params.clone();
305 for &inst in &insts {
306 defined.extend(func[inst].results());
307 }
308 Ok(Candidate { id, header, entry, body, defined })
309 }
310}
311
312pub(crate) fn repeatable(func: &Func, inst: Inst) -> bool {
322 let data = func[inst];
323 if data.opcode.has_effects() || func.carries_mem(inst) {
324 return false;
325 }
326 matches!(
327 data.extra.kind(),
328 ExtraKind::None
329 | ExtraKind::Imm
330 | ExtraKind::Symbol
331 | ExtraKind::IntPred
332 | ExtraKind::FloatPred
333 )
334}
335
336fn carried(
351 func: &Func,
352 dom: &Dominators,
353 loops: &Loops,
354 wanted: Vec<Candidate>,
355 stats: &mut Stats,
356 say: bool,
357) -> Vec<Job> {
358 let mut watched: HashMap<Value, usize> = HashMap::new();
359 for (which, candidate) in wanted.iter().enumerate() {
360 for &value in &candidate.defined {
361 watched.insert(value, which);
362 }
363 }
364 let mut read: Vec<HashSet<Value>> = vec![HashSet::new(); wanted.len()];
365 let mut escapes = vec![false; wanted.len()];
366 let mut names: Vec<usize> = Vec::new();
367 for block in func.blocks() {
368 names.clear();
369 for inst in func.insts(block) {
370 reads(func, inst, block, &wanted, &watched, &mut read, &mut names);
371 }
372 for &which in &names {
373 let candidate = &wanted[which];
374 if !loops.contains(candidate.id, block) || !dom.dominates(candidate.body, block) {
375 escapes[which] = true;
376 }
377 }
378 }
379 let mut jobs = Vec::new();
380 for (which, candidate) in wanted.into_iter().enumerate() {
381 if escapes[which] {
382 if say {
383 stats.missed(ESCAPES);
384 }
385 continue;
386 }
387 let taken = &read[which];
388 let carried = candidate.defined.into_iter().filter(|value| taken.contains(value)).collect();
389 jobs.push(Job {
390 id: candidate.id,
391 header: candidate.header,
392 entry: candidate.entry,
393 body: candidate.body,
394 carried,
395 blocks: Vec::new(),
396 });
397 }
398 jobs
399}
400
401fn reads(
409 func: &Func,
410 inst: Inst,
411 block: Block,
412 wanted: &[Candidate],
413 watched: &HashMap<Value, usize>,
414 read: &mut [HashSet<Value>],
415 names: &mut Vec<usize>,
416) {
417 let mut note = |value: Value| {
418 let Some(&which) = watched.get(&value) else { return };
419 if block == wanted[which].header {
420 return;
421 }
422 read[which].insert(value);
423 if !names.contains(&which) {
424 names.push(which);
425 }
426 };
427 for &value in &func[func[inst].args] {
428 note(value);
429 }
430 for call in func.successors(inst) {
431 for &value in &func[call.args] {
432 note(value);
433 }
434 }
435}
436
437fn independent(loops: &Loops, jobs: Vec<Job>) -> Vec<Job> {
454 let mut blocked = vec![false; loops.count()];
455 let mut taken = vec![false; loops.count()];
456 let mut kept: Vec<Job> = Vec::new();
457 for job in jobs {
458 if blocked[job.id.index()] || inside(loops, &taken, job.entry) {
459 continue;
460 }
461 let mut up = Some(job.id);
462 while let Some(id) = up {
463 blocked[id.index()] = true;
464 up = loops.parent(id);
465 }
466 let mut down = vec![job.id];
467 while let Some(id) = down.pop() {
468 blocked[id.index()] = true;
469 down.extend(loops.children(id));
470 }
471 let mut around = loops.innermost(job.entry);
473 while let Some(id) = around {
474 blocked[id.index()] = true;
475 around = loops.parent(id);
476 }
477 taken[job.id.index()] = true;
478 kept.push(job);
479 }
480 kept
481}
482
483fn inside(loops: &Loops, taken: &[bool], block: Block) -> bool {
485 let mut walk = loops.innermost(block);
486 while let Some(id) = walk {
487 if taken[id.index()] {
488 return true;
489 }
490 walk = loops.parent(id);
491 }
492 false
493}
494
495fn apply(func: &mut Func, job: &Job) -> Block {
497 let term = func.terminator(job.header).expect("the plan read this terminator");
498 let entry_term = func.terminator(job.entry).expect("a preheader ends in a jump");
499 let incoming = edge_args(func, entry_term, job.header);
502 let mut map: HashMap<Value, Value> = HashMap::new();
503 for (¶m, &arg) in func[job.header].params.clone().iter().zip(&incoming) {
504 map.insert(param, arg);
505 }
506 let copy = func.create_block();
507 let insts: Vec<Inst> = func.insts(job.header).filter(|&inst| inst != term).collect();
508 for inst in insts {
509 clone_into(func, copy, inst, &mut map);
510 }
511 clone_branch(func, copy, term, &map);
512 for at in func.target_list(entry_term).iter() {
513 let call = func[at];
514 if call.block == job.header {
515 func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY, ..call });
516 }
517 }
518 for &value in &job.carried {
519 let arrived = map.get(&value).copied().unwrap_or(value);
520 merge(func, job, copy, value, arrived);
521 }
522 copy
523}
524
525fn edge_args(func: &Func, term: Inst, to: Block) -> Vec<Value> {
527 for call in func.successors(term) {
528 if call.block == to {
529 return func[call.args].to_vec();
530 }
531 }
532 Vec::new()
533}
534
535pub(crate) fn clone_into(
537 func: &mut Func,
538 into: Block,
539 inst: Inst,
540 map: &mut HashMap<Value, Value>,
541) {
542 let data = func[inst];
543 let args: Vec<Value> =
544 func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
545 let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
546 let span = func.span(inst);
547 let args = func.push_values(&args);
548 let fresh = func.create_inst(InstData { args, ..data }, &types, span);
549 func.append_inst(into, fresh);
550 for (old, new) in data.results().zip(func[fresh].results()) {
551 map.insert(old, new);
552 }
553}
554
555fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
561 let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
562 let cond = at(&func[func[term].args][0]);
563 let calls: Vec<BlockCall> = func.successors(term).collect();
564 let args: Vec<Vec<Value>> =
565 calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
566 Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
567}
568
569fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
581 let param = func.append_param(job.body, func[value].ty);
582 for &block in job.blocks.iter().chain([©]) {
583 let Some(term) = func.terminator(block) else { continue };
584 let carry = if block == job.header {
585 value
586 } else if block == copy {
587 arrived
588 } else {
589 param
590 };
591 for at in func.target_list(term).iter() {
592 let call = func[at];
593 if call.block != job.body {
594 continue;
595 }
596 let args = func.append_arg(call.args, carry);
597 func.set_block_call(at, BlockCall { args, ..call });
598 }
599 }
600 for decl in func.value_decls(value).collect::<Vec<u32>>() {
606 func.declare_value(param, decl);
607 }
608 let elsewhere: Vec<Start> = func
609 .value_starts(value)
610 .filter(|&start| {
611 func.start_place(start).map_or(start.block, |(block, _)| block) != job.header
612 })
613 .collect();
614 func.move_starts(value, param, &elsewhere);
615 for &block in &job.blocks {
619 if block == job.header {
620 continue;
621 }
622 for inst in func.insts(block).collect::<Vec<_>>() {
623 let swap = |had: Value| if had == value { param } else { had };
624 func.rewrite(func[inst].args, swap);
625 for at in func.target_list(inst).iter() {
626 func.rewrite(func[at].args, swap);
627 }
628 }
629 }
630}
631
632fn settle(func: &mut Func, an: &mut Analyses, copies: &[Block], stats: &mut Stats) -> bool {
650 let mut out: Vec<(Inst, BlockCall, bool)> = Vec::new();
651 {
652 let cfg = an.cfg(func);
653 let dom = an.dominators(func);
654 let mut ranges = Ranges::new(func, cfg, dom);
655 for © in copies {
656 let Some(term) = func.terminator(copy) else { continue };
657 let cond = func[func[term].args][0];
658 let Some(taken) = prune::settled(func, &mut ranges, copy, cond) else {
659 stats.missed(UNDECIDED);
660 continue;
661 };
662 let calls: Vec<BlockCall> = func.successors(term).collect();
663 out.push((term, if taken { calls[0] } else { calls[1] }, taken));
664 }
665 }
666 if out.is_empty() {
667 return false;
668 }
669 for (term, call, taken) in out {
670 simplify_cfg::jump_to(func, term, call);
671 stats.optimized(if taken { ENTERED } else { SKIPPED });
672 }
673 true
674}
675
676#[cfg(test)]
677mod tests {
678 use rucc_base::Interner;
679 use rucc_ir::{
680 Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
681 Signature, Start, Type, verify_func,
682 };
683 use rucc_target::{TargetInfo, Triple};
684
685 use super::{HeaderCopy, SIZE, SPEED};
686 use crate::canon::Canon;
687 use crate::cfg::Cfg;
688 use crate::dom::Dominators;
689 use crate::loops::Loops;
690 use crate::stats::Kind;
691 use crate::{Fuel, Pass, Stats};
692
693 fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
699 let mut an = crate::machine::fixtures::analyses();
700 Canon.run(func, &mut an, &mut Fuel::unlimited());
701 pass.run(func, &mut an, &mut Fuel::unlimited())
702 }
703
704 fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
706 let cfg = Cfg::new(func);
707 let dom = Dominators::new(&cfg);
708 let loops = Loops::new(&cfg, &dom);
709 (cfg, dom, loops)
710 }
711
712 fn sound(func: &Func, names: &mut Interner) {
718 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
719 let module = Module::new(names.intern("t.c"), &target);
720 if let Err(errors) = verify_func(&module, func, names) {
721 panic!("{errors:#?}");
722 }
723 }
724
725 fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
737 let mut names = Interner::new();
738 let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
739 let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
740 let mut func = Func::new(names.intern("f"), signature);
741 let entry = func.create_block();
742 let head = func.create_block();
743 let body = func.create_block();
744 let done = func.create_block();
745 let limit = match bound {
746 Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
747 None => func.append_param(entry, Type::int(32)),
748 };
749 let i = func.append_param(head, Type::int(32));
750 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
751 Builder::new(&mut func, entry).jump(head, &[zero]);
752 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
753 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
754 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
755 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
756 Builder::new(&mut func, body).jump(head, &[next]);
757 Builder::new(&mut func, done).ret(&[i]);
758 (func, names, vec![entry, head, body, done])
759 }
760
761 fn tests_at_the_top(func: &Func) -> bool {
763 let (cfg, dom, loops) = forest(func);
764 let _ = dom;
765 let id = loops.all().next().expect("there is a loop");
766 let header = loops.header(id);
767 cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
768 }
769
770 #[test]
771 fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
772 let (mut func, mut names, _) = counted(None);
773 assert!(tests_at_the_top(&func), "the shape this pass is for");
774
775 let stats = copied(&mut func, &SPEED);
776 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
777 assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
778 sound(&func, &mut names);
779 }
780
781 #[test]
782 fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
783 let (mut func, mut names, blocks) = counted(None);
784 let body = blocks[2];
785 assert!(func[body].params.is_empty(), "the body carries nothing to start with");
786
787 copied(&mut func, &SPEED);
788 assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
789 assert_eq!(
790 Cfg::new(&func).predecessors(body).len(),
791 2,
792 "one edge from the header and one from the copy"
793 );
794 sound(&func, &mut names);
795 }
796
797 #[test]
802 fn a_name_on_the_value_the_body_now_takes_as_a_parameter_goes_with_it() {
803 let (mut func, mut names, blocks) = counted(None);
804 let (head, body) = (blocks[1], blocks[2]);
805 let i = func[head].params[0];
806 let test = func.insts(head).next();
807 func.declare_value(i, 3);
808 func.declare_value_from(i, Start { decl: 4, block: body, after: None });
809 func.declare_value_from(i, Start { decl: 5, block: head, after: test });
810
811 copied(&mut func, &SPEED);
812 let param = func[body].params[0];
813 assert_eq!(func.value_decls(param).collect::<Vec<u32>>(), vec![3]);
814 assert_eq!(func.value_decls(i).collect::<Vec<u32>>(), vec![3], "still the header's");
815 let decls = |value| func.value_starts(value).map(|start| start.decl).collect::<Vec<u32>>();
816 assert_eq!(decls(param), vec![4], "the body's start is on the body's parameter");
817 assert_eq!(decls(i), vec![5], "and the header's stays on the header's value");
818 sound(&func, &mut names);
819 }
820
821 #[test]
822 fn an_entry_test_the_ranges_settle_is_taken_out() {
823 let (mut func, mut names, _) = counted(Some(10));
824
825 let stats = copied(&mut func, &SPEED);
826 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
827 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
828 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
829 sound(&func, &mut names);
830
831 let (cfg, _dom, loops) = forest(&func);
832 let id = loops.all().next().expect("the loop is still there");
833 let entry = func.entry().expect("there is an entry");
834 assert!(cfg.reaches(loops.header(id)), "and it is still reached");
835 assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
836 }
837
838 #[test]
839 fn a_loop_the_ranges_say_never_runs_is_removed() {
840 let (mut func, mut names, blocks) = counted(Some(0));
841
842 let stats = copied(&mut func, &SPEED);
843 assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
844 sound(&func, &mut names);
845
846 let (_cfg, _dom, loops) = forest(&func);
847 assert_eq!(loops.count(), 0, "there is no loop left");
848 assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
849 }
850
851 #[test]
852 fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
853 let (mut func, _names, _) = counted(None);
854
855 let stats = copied(&mut func, &SPEED);
856 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
857 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
858 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
859 }
860
861 #[test]
862 fn a_second_run_changes_nothing() {
863 let (mut func, mut names, _) = counted(None);
864 copied(&mut func, &SPEED);
865 let again =
866 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
867 assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
868 assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
869 sound(&func, &mut names);
870 }
871
872 #[test]
873 fn a_header_that_writes_to_memory_is_left_alone() {
874 let mut names = Interner::new();
875 let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
876 let mut func = Func::new(names.intern("f"), signature);
877 let entry = func.create_block();
878 let head = func.create_block();
879 let body = func.create_block();
880 let done = func.create_block();
881 let limit = func.append_param(entry, Type::int(32));
882 let addr = func.append_param(entry, Type::PTR);
883 let i = func.append_param(head, Type::int(32));
884 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
885 Builder::new(&mut func, entry).jump(head, &[zero]);
886 let access = MemInfo {
887 size: 4,
888 align: 4,
889 order: MemOrder::NotAtomic,
890 tbaa: None,
891 owns: 0,
892 restrict: Restrict::NONE,
893 };
894 Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
895 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
896 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
897 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
898 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
899 Builder::new(&mut func, body).jump(head, &[next]);
900 Builder::new(&mut func, done).ret(&[]);
901
902 let stats = copied(&mut func, &SPEED);
903 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
904 assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
905 assert!(tests_at_the_top(&func), "the loop is exactly as it was");
906 }
907
908 #[test]
909 fn a_header_larger_than_the_level_allows_is_left_alone() {
910 let stats = copied(&mut padded(6), &SIZE);
913 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
914 assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
915
916 let stats = copied(&mut padded(6), &SPEED);
917 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
918 }
919
920 fn padded(extra: usize) -> Func {
922 let (mut func, _names, blocks) = counted(None);
923 let head = blocks[1];
924 let term = func.terminator(head).expect("the header branches");
925 for _ in 0..extra {
926 let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
927 let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
928 func.remove_inst(inst);
929 func.insert_before(inst, term);
930 }
931 func
932 }
933
934 #[test]
935 fn fuel_stops_the_copy_where_it_stands() {
936 let (mut func, _names, _) = counted(None);
937 let mut an = crate::machine::fixtures::analyses();
938 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
939
940 let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
941 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
942 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
943 assert!(tests_at_the_top(&func), "and the loop is as it was");
944 }
945
946 #[test]
947 fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
948 let (mut func, _names, _) = counted(None);
953
954 let stats =
955 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
956 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
957 assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
958 assert!(tests_at_the_top(&func), "and the loop is as it was");
959 }
960
961 #[test]
962 fn a_loop_with_no_preheader_is_declined() {
963 let mut names = Interner::new();
964 let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
965 let mut func = Func::new(names.intern("f"), signature);
966 let entry = func.create_block();
967 let one = func.create_block();
968 let two = func.create_block();
969 let head = func.create_block();
970 let body = func.create_block();
971 let done = func.create_block();
972 let c = func.append_param(entry, Type::int(1));
973 let limit = func.append_param(entry, Type::int(32));
974 let i = func.append_param(head, Type::int(32));
975 Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
976 let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
977 Builder::new(&mut func, one).jump(head, &[zero]);
978 let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
979 Builder::new(&mut func, two).jump(head, &[start]);
980 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
981 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
982 Builder::new(&mut func, body).jump(head, &[i]);
983 Builder::new(&mut func, done).ret(&[]);
984
985 let stats =
986 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
987 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
988 assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
989
990 let stats = copied(&mut func, &SPEED);
992 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
993 sound(&func, &mut names);
994 }
995
996 fn side_by_side() -> (Func, Interner, Vec<Block>) {
1008 let mut names = Interner::new();
1009 let signature = Signature::new().with_params(&[Type::int(32)]);
1010 let mut func = Func::new(names.intern("f"), signature);
1011 let entry = func.create_block();
1012 let one = func.create_block();
1013 let up = func.create_block();
1014 let mid = func.create_block();
1015 let two = func.create_block();
1016 let down = func.create_block();
1017 let done = func.create_block();
1018 let n = func.append_param(entry, Type::int(32));
1019 let i = func.append_param(one, Type::int(32));
1020 let j = func.append_param(two, Type::int(32));
1021 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1022 Builder::new(&mut func, entry).jump(one, &[zero]);
1023 let t = Builder::new(&mut func, one).icmp(IntPred::Slt, i, n);
1024 Builder::new(&mut func, one).br_if(t, up, &[], mid, &[]);
1025 let step = Builder::new(&mut func, up).iconst(Type::int(32), 1);
1026 let next = Builder::new(&mut func, up).binary(Opcode::Add, i, step, Flags::NONE);
1027 Builder::new(&mut func, up).jump(one, &[next]);
1028 let start = Builder::new(&mut func, mid).iconst(Type::int(32), 0);
1029 Builder::new(&mut func, mid).jump(two, &[start]);
1030 let u = Builder::new(&mut func, two).icmp(IntPred::Slt, j, n);
1031 Builder::new(&mut func, two).br_if(u, down, &[], done, &[]);
1032 let stride = Builder::new(&mut func, down).iconst(Type::int(32), 1);
1033 let after = Builder::new(&mut func, down).binary(Opcode::Add, j, stride, Flags::NONE);
1034 Builder::new(&mut func, down).jump(two, &[after]);
1035 Builder::new(&mut func, done).ret(&[]);
1036 (func, names, vec![up, down])
1037 }
1038
1039 #[test]
1040 fn two_loops_that_do_not_meet_are_both_copied() {
1041 let (mut func, mut names, bodies) = side_by_side();
1042
1043 let stats = copied(&mut func, &SPEED);
1044 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1045 sound(&func, &mut names);
1046
1047 let (_cfg, _dom, loops) = forest(&func);
1048 assert_eq!(loops.count(), 2, "both loops are still loops");
1049 for id in loops.all() {
1050 let header = loops.header(id);
1051 assert!(
1052 !Cfg::new(&func).successors(header).iter().any(|&to| !loops.contains(id, to)),
1053 "and neither of them tests at the top any more"
1054 );
1055 }
1056 for body in bodies {
1057 assert_eq!(func[body].params.len(), 1, "each body carries its own counter");
1058 }
1059 }
1060
1061 fn nested() -> (Func, Interner) {
1073 let mut names = Interner::new();
1074 let signature = Signature::new().with_params(&[Type::int(32)]);
1075 let mut func = Func::new(names.intern("f"), signature);
1076 let entry = func.create_block();
1077 let outer = func.create_block();
1078 let ahead = func.create_block();
1079 let inner = func.create_block();
1080 let under = func.create_block();
1081 let latch = func.create_block();
1082 let done = func.create_block();
1083 let n = func.append_param(entry, Type::int(32));
1084 let i = func.append_param(outer, Type::int(32));
1085 let j = func.append_param(inner, Type::int(32));
1086 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1087 Builder::new(&mut func, entry).jump(outer, &[zero]);
1088 let t = Builder::new(&mut func, outer).icmp(IntPred::Slt, i, n);
1089 Builder::new(&mut func, outer).br_if(t, ahead, &[], done, &[]);
1090 let start = Builder::new(&mut func, ahead).iconst(Type::int(32), 0);
1091 Builder::new(&mut func, ahead).jump(inner, &[start]);
1092 let u = Builder::new(&mut func, inner).icmp(IntPred::Slt, j, n);
1093 Builder::new(&mut func, inner).br_if(u, under, &[], latch, &[]);
1094 let stride = Builder::new(&mut func, under).iconst(Type::int(32), 1);
1095 let after = Builder::new(&mut func, under).binary(Opcode::Add, j, stride, Flags::NONE);
1096 Builder::new(&mut func, under).jump(inner, &[after]);
1097 let step = Builder::new(&mut func, latch).iconst(Type::int(32), 1);
1098 let next = Builder::new(&mut func, latch).binary(Opcode::Add, i, step, Flags::NONE);
1099 Builder::new(&mut func, latch).jump(outer, &[next]);
1100 Builder::new(&mut func, done).ret(&[]);
1101 (func, names)
1102 }
1103
1104 #[test]
1105 fn a_loop_and_the_loop_inside_it_are_copied_one_round_apart() {
1106 let (mut func, mut names) = nested();
1111
1112 let stats = copied(&mut func, &SPEED);
1113 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1114 sound(&func, &mut names);
1115
1116 let (cfg, _dom, loops) = forest(&func);
1117 assert_eq!(loops.count(), 2, "both loops survived the copy");
1118 for id in loops.all() {
1119 let header = loops.header(id);
1120 assert!(
1121 !cfg.successors(header).iter().any(|&to| !loops.contains(id, to)),
1122 "and both test at the bottom now"
1123 );
1124 }
1125 }
1126
1127 #[test]
1128 fn a_round_stops_where_the_fuel_does() {
1129 let (mut func, mut names) = {
1133 let (mut func, names, _) = side_by_side();
1134 let mut an = crate::machine::fixtures::analyses();
1135 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
1136 (func, names)
1137 };
1138
1139 let stats =
1140 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1141 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
1142 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1143 sound(&func, &mut names);
1144 }
1145}