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::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 while let Some(job) = self.plan(func, an, &done, &mut stats, say) {
140 say = false;
141 if !fuel.take() {
142 stats.missed(NO_FUEL);
143 break;
144 }
145 done.insert(job.header);
146 done.insert(job.body);
147 let copy = apply(func, &job);
148 stats.optimized(COPIED);
149 an.clear();
150 if settle(func, an, copy, &mut stats) {
151 an.clear();
152 }
153 }
154 if stats.changed() {
155 simplify_cfg::sweep(func, an, &mut stats);
158 }
159 an.clear();
160 stats
161 }
162}
163
164#[derive(Debug)]
166struct Job {
167 header: Block,
169 entry: Block,
171 body: Block,
177 carried: Vec<Value>,
180}
181
182impl HeaderCopy {
183 fn plan(
190 &self,
191 func: &Func,
192 an: &mut Analyses,
193 done: &HashSet<Block>,
194 stats: &mut Stats,
195 say: bool,
196 ) -> Option<Job> {
197 let (cfg, dom, loops) = (an.cfg(func), an.dominators(func), an.loops(func));
198 let mut found = None;
199 for id in loops.all() {
200 let header = loops.header(id);
201 if done.contains(&header) {
202 continue;
203 }
204 match self.consider(func, cfg, dom, loops, id, header) {
205 Ok(job) => {
206 if found.is_none() {
207 found = Some(job);
208 }
209 if !say {
210 break;
211 }
212 }
213 Err(why) if say && why == ALREADY => stats.note(ALREADY),
214 Err(why) if say => stats.missed(why),
215 Err(_) => (),
216 }
217 }
218 found
219 }
220
221 fn consider(
223 &self,
224 func: &Func,
225 cfg: &Cfg,
226 dom: &Dominators,
227 loops: &Loops,
228 id: crate::loops::LoopId,
229 header: Block,
230 ) -> Result<Job, &'static str> {
231 let leaves = cfg.successors(header).iter().any(|&to| !loops.contains(id, to));
232 if !leaves {
233 return Err(ALREADY);
236 }
237 let entry = loops.preheader(cfg, id).ok_or(NO_PREHEADER)?;
238 let term = func.terminator(header).ok_or(SHAPE)?;
239 if func[term].opcode != Opcode::BrIf {
240 return Err(SHAPE);
241 }
242 let calls: Vec<BlockCall> = func.successors(term).collect();
243 let [then_call, else_call] = calls[..].try_into().map_err(|_| SHAPE)?;
244 let body = match (loops.contains(id, then_call.block), loops.contains(id, else_call.block))
245 {
246 (true, false) => then_call.block,
247 (false, true) => else_call.block,
248 _ => return Err(SHAPE),
249 };
250 if body == header {
251 return Err(SHAPE);
252 }
253 let insts: Vec<Inst> = func.insts(header).filter(|&inst| inst != term).collect();
254 if insts.len() > self.budget as usize {
255 return Err(TOO_BIG);
256 }
257 for &inst in &insts {
258 if !repeatable(func, inst) {
259 return Err(EFFECTS);
260 }
261 }
262 let carried = carried(func, dom, loops, id, header, body, &insts)?;
263 Ok(Job { header, entry, body, carried })
264 }
265}
266
267fn repeatable(func: &Func, inst: Inst) -> bool {
277 let data = func[inst];
278 if data.opcode.has_effects() || func.carries_mem(inst) {
279 return false;
280 }
281 matches!(
282 data.extra.kind(),
283 ExtraKind::None
284 | ExtraKind::Imm
285 | ExtraKind::Symbol
286 | ExtraKind::IntPred
287 | ExtraKind::FloatPred
288 )
289}
290
291fn carried(
297 func: &Func,
298 dom: &Dominators,
299 loops: &Loops,
300 id: crate::loops::LoopId,
301 header: Block,
302 body: Block,
303 insts: &[Inst],
304) -> Result<Vec<Value>, &'static str> {
305 let mut defined: Vec<Value> = func[header].params.clone();
306 for &inst in insts {
307 defined.extend(func[inst].results());
308 }
309 let watched: HashSet<Value> = defined.iter().copied().collect();
313 let mut read: HashSet<Value> = HashSet::new();
314 for block in func.blocks() {
315 if block == header {
316 continue;
317 }
318 let mut names = false;
319 for inst in func.insts(block) {
320 names |= reads(func, inst, &watched, &mut read);
321 }
322 if names && (!loops.contains(id, block) || !dom.dominates(body, block)) {
323 return Err(ESCAPES);
324 }
325 }
326 Ok(defined.into_iter().filter(|value| read.contains(value)).collect())
327}
328
329fn reads(func: &Func, inst: Inst, watched: &HashSet<Value>, read: &mut HashSet<Value>) -> bool {
334 let mut named = false;
335 for &value in &func[func[inst].args] {
336 if watched.contains(&value) {
337 read.insert(value);
338 named = true;
339 }
340 }
341 for call in func.successors(inst) {
342 for &value in &func[call.args] {
343 if watched.contains(&value) {
344 read.insert(value);
345 named = true;
346 }
347 }
348 }
349 named
350}
351
352fn apply(func: &mut Func, job: &Job) -> Block {
354 let term = func.terminator(job.header).expect("the plan read this terminator");
355 let entry_term = func.terminator(job.entry).expect("a preheader ends in a jump");
356 let incoming = edge_args(func, entry_term, job.header);
359 let mut map: HashMap<Value, Value> = HashMap::new();
360 for (¶m, &arg) in func[job.header].params.clone().iter().zip(&incoming) {
361 map.insert(param, arg);
362 }
363 let copy = func.create_block();
364 let insts: Vec<Inst> = func.insts(job.header).filter(|&inst| inst != term).collect();
365 for inst in insts {
366 clone_into(func, copy, inst, &mut map);
367 }
368 clone_branch(func, copy, term, &map);
369 for at in func.target_list(entry_term).iter() {
370 let call = func[at];
371 if call.block == job.header {
372 func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY, ..call });
373 }
374 }
375 for &value in &job.carried {
376 let arrived = map.get(&value).copied().unwrap_or(value);
377 merge(func, job, copy, value, arrived);
378 }
379 copy
380}
381
382fn edge_args(func: &Func, term: Inst, to: Block) -> Vec<Value> {
384 for call in func.successors(term) {
385 if call.block == to {
386 return func[call.args].to_vec();
387 }
388 }
389 Vec::new()
390}
391
392fn clone_into(func: &mut Func, into: Block, inst: Inst, map: &mut HashMap<Value, Value>) {
394 let data = func[inst];
395 let args: Vec<Value> =
396 func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
397 let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
398 let span = func.span(inst);
399 let args = func.push_values(&args);
400 let fresh = func.create_inst(InstData { args, ..data }, &types, span);
401 func.append_inst(into, fresh);
402 for (old, new) in data.results().zip(func[fresh].results()) {
403 map.insert(old, new);
404 }
405}
406
407fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
413 let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
414 let cond = at(&func[func[term].args][0]);
415 let calls: Vec<BlockCall> = func.successors(term).collect();
416 let args: Vec<Vec<Value>> =
417 calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
418 Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
419}
420
421fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
429 let param = func.append_param(job.body, func[value].ty);
430 for block in func.blocks().collect::<Vec<_>>() {
431 let Some(term) = func.terminator(block) else { continue };
432 let carry = if block == job.header {
433 value
434 } else if block == copy {
435 arrived
436 } else {
437 param
438 };
439 for at in func.target_list(term).iter() {
440 let call = func[at];
441 if call.block != job.body {
442 continue;
443 }
444 let args = func.append_arg(call.args, carry);
445 func.set_block_call(at, BlockCall { args, ..call });
446 }
447 }
448 for block in func.blocks().collect::<Vec<_>>() {
452 if block == job.header || block == copy {
453 continue;
454 }
455 for inst in func.insts(block).collect::<Vec<_>>() {
456 let swap = |had: Value| if had == value { param } else { had };
457 func.rewrite(func[inst].args, swap);
458 for at in func.target_list(inst).iter() {
459 func.rewrite(func[at].args, swap);
460 }
461 }
462 }
463}
464
465fn settle(func: &mut Func, an: &mut Analyses, copy: Block, stats: &mut Stats) -> bool {
478 let Some(term) = func.terminator(copy) else { return false };
479 let cond = func[func[term].args][0];
480 let answer = {
481 let cfg = an.cfg(func);
482 let dom = an.dominators(func);
483 let mut ranges = Ranges::new(func, cfg, dom);
484 prune::settled(func, &mut ranges, copy, cond)
485 };
486 let Some(taken) = answer else {
487 stats.missed(UNDECIDED);
488 return false;
489 };
490 let calls: Vec<BlockCall> = func.successors(term).collect();
491 let call = if taken { calls[0] } else { calls[1] };
492 simplify_cfg::jump_to(func, term, call);
493 stats.optimized(if taken { ENTERED } else { SKIPPED });
494 true
495}
496
497#[cfg(test)]
498mod tests {
499 use rucc_base::Interner;
500 use rucc_ir::{
501 Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
502 Signature, Type, verify_func,
503 };
504 use rucc_target::{TargetInfo, Triple};
505
506 use super::{HeaderCopy, SIZE, SPEED};
507 use crate::canon::Canon;
508 use crate::cfg::Cfg;
509 use crate::dom::Dominators;
510 use crate::loops::Loops;
511 use crate::stats::Kind;
512 use crate::{Fuel, Pass, Stats};
513
514 fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
520 let mut an = crate::machine::fixtures::analyses();
521 Canon.run(func, &mut an, &mut Fuel::unlimited());
522 pass.run(func, &mut an, &mut Fuel::unlimited())
523 }
524
525 fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
527 let cfg = Cfg::new(func);
528 let dom = Dominators::new(&cfg);
529 let loops = Loops::new(&cfg, &dom);
530 (cfg, dom, loops)
531 }
532
533 fn sound(func: &Func, names: &mut Interner) {
539 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
540 let module = Module::new(names.intern("t.c"), &target);
541 if let Err(errors) = verify_func(&module, func, names) {
542 panic!("{errors:#?}");
543 }
544 }
545
546 fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
558 let mut names = Interner::new();
559 let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
560 let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
561 let mut func = Func::new(names.intern("f"), signature);
562 let entry = func.create_block();
563 let head = func.create_block();
564 let body = func.create_block();
565 let done = func.create_block();
566 let limit = match bound {
567 Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
568 None => func.append_param(entry, Type::int(32)),
569 };
570 let i = func.append_param(head, Type::int(32));
571 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
572 Builder::new(&mut func, entry).jump(head, &[zero]);
573 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
574 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
575 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
576 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
577 Builder::new(&mut func, body).jump(head, &[next]);
578 Builder::new(&mut func, done).ret(&[i]);
579 (func, names, vec![entry, head, body, done])
580 }
581
582 fn tests_at_the_top(func: &Func) -> bool {
584 let (cfg, dom, loops) = forest(func);
585 let _ = dom;
586 let id = loops.all().next().expect("there is a loop");
587 let header = loops.header(id);
588 cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
589 }
590
591 #[test]
592 fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
593 let (mut func, mut names, _) = counted(None);
594 assert!(tests_at_the_top(&func), "the shape this pass is for");
595
596 let stats = copied(&mut func, &SPEED);
597 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
598 assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
599 sound(&func, &mut names);
600 }
601
602 #[test]
603 fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
604 let (mut func, mut names, blocks) = counted(None);
605 let body = blocks[2];
606 assert!(func[body].params.is_empty(), "the body carries nothing to start with");
607
608 copied(&mut func, &SPEED);
609 assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
610 assert_eq!(
611 Cfg::new(&func).predecessors(body).len(),
612 2,
613 "one edge from the header and one from the copy"
614 );
615 sound(&func, &mut names);
616 }
617
618 #[test]
619 fn an_entry_test_the_ranges_settle_is_taken_out() {
620 let (mut func, mut names, _) = counted(Some(10));
621
622 let stats = copied(&mut func, &SPEED);
623 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
624 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
625 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
626 sound(&func, &mut names);
627
628 let (cfg, _dom, loops) = forest(&func);
629 let id = loops.all().next().expect("the loop is still there");
630 let entry = func.entry().expect("there is an entry");
631 assert!(cfg.reaches(loops.header(id)), "and it is still reached");
632 assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
633 }
634
635 #[test]
636 fn a_loop_the_ranges_say_never_runs_is_removed() {
637 let (mut func, mut names, blocks) = counted(Some(0));
638
639 let stats = copied(&mut func, &SPEED);
640 assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
641 sound(&func, &mut names);
642
643 let (_cfg, _dom, loops) = forest(&func);
644 assert_eq!(loops.count(), 0, "there is no loop left");
645 assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
646 }
647
648 #[test]
649 fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
650 let (mut func, _names, _) = counted(None);
651
652 let stats = copied(&mut func, &SPEED);
653 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
654 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
655 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
656 }
657
658 #[test]
659 fn a_second_run_changes_nothing() {
660 let (mut func, mut names, _) = counted(None);
661 copied(&mut func, &SPEED);
662 let again =
663 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
664 assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
665 assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
666 sound(&func, &mut names);
667 }
668
669 #[test]
670 fn a_header_that_writes_to_memory_is_left_alone() {
671 let mut names = Interner::new();
672 let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
673 let mut func = Func::new(names.intern("f"), signature);
674 let entry = func.create_block();
675 let head = func.create_block();
676 let body = func.create_block();
677 let done = func.create_block();
678 let limit = func.append_param(entry, Type::int(32));
679 let addr = func.append_param(entry, Type::PTR);
680 let i = func.append_param(head, Type::int(32));
681 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
682 Builder::new(&mut func, entry).jump(head, &[zero]);
683 let access = MemInfo {
684 size: 4,
685 align: 4,
686 order: MemOrder::NotAtomic,
687 tbaa: None,
688 owns: 0,
689 restrict: Restrict::NONE,
690 };
691 Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
692 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
693 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
694 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
695 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
696 Builder::new(&mut func, body).jump(head, &[next]);
697 Builder::new(&mut func, done).ret(&[]);
698
699 let stats = copied(&mut func, &SPEED);
700 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
701 assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
702 assert!(tests_at_the_top(&func), "the loop is exactly as it was");
703 }
704
705 #[test]
706 fn a_header_larger_than_the_level_allows_is_left_alone() {
707 let stats = copied(&mut padded(6), &SIZE);
710 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
711 assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
712
713 let stats = copied(&mut padded(6), &SPEED);
714 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
715 }
716
717 fn padded(extra: usize) -> Func {
719 let (mut func, _names, blocks) = counted(None);
720 let head = blocks[1];
721 let term = func.terminator(head).expect("the header branches");
722 for _ in 0..extra {
723 let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
724 let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
725 func.remove_inst(inst);
726 func.insert_before(inst, term);
727 }
728 func
729 }
730
731 #[test]
732 fn fuel_stops_the_copy_where_it_stands() {
733 let (mut func, _names, _) = counted(None);
734 let mut an = crate::machine::fixtures::analyses();
735 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
736
737 let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
738 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
739 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
740 assert!(tests_at_the_top(&func), "and the loop is as it was");
741 }
742
743 #[test]
744 fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
745 let (mut func, _names, _) = counted(None);
750
751 let stats =
752 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
753 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
754 assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
755 assert!(tests_at_the_top(&func), "and the loop is as it was");
756 }
757
758 #[test]
759 fn a_loop_with_no_preheader_is_declined() {
760 let mut names = Interner::new();
761 let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
762 let mut func = Func::new(names.intern("f"), signature);
763 let entry = func.create_block();
764 let one = func.create_block();
765 let two = func.create_block();
766 let head = func.create_block();
767 let body = func.create_block();
768 let done = func.create_block();
769 let c = func.append_param(entry, Type::int(1));
770 let limit = func.append_param(entry, Type::int(32));
771 let i = func.append_param(head, Type::int(32));
772 Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
773 let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
774 Builder::new(&mut func, one).jump(head, &[zero]);
775 let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
776 Builder::new(&mut func, two).jump(head, &[start]);
777 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
778 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
779 Builder::new(&mut func, body).jump(head, &[i]);
780 Builder::new(&mut func, done).ret(&[]);
781
782 let stats =
783 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
784 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
785 assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
786
787 let stats = copied(&mut func, &SPEED);
789 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
790 sound(&func, &mut names);
791 }
792}