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 if func[at].block == job.header {
371 func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY });
372 }
373 }
374 for &value in &job.carried {
375 let arrived = map.get(&value).copied().unwrap_or(value);
376 merge(func, job, copy, value, arrived);
377 }
378 copy
379}
380
381fn edge_args(func: &Func, term: Inst, to: Block) -> Vec<Value> {
383 for call in func.successors(term) {
384 if call.block == to {
385 return func[call.args].to_vec();
386 }
387 }
388 Vec::new()
389}
390
391fn clone_into(func: &mut Func, into: Block, inst: Inst, map: &mut HashMap<Value, Value>) {
393 let data = func[inst];
394 let args: Vec<Value> =
395 func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
396 let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
397 let span = func.span(inst);
398 let args = func.push_values(&args);
399 let fresh = func.create_inst(InstData { args, ..data }, &types, span);
400 func.append_inst(into, fresh);
401 for (old, new) in data.results().zip(func[fresh].results()) {
402 map.insert(old, new);
403 }
404}
405
406fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
412 let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
413 let cond = at(&func[func[term].args][0]);
414 let calls: Vec<BlockCall> = func.successors(term).collect();
415 let args: Vec<Vec<Value>> =
416 calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
417 Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
418}
419
420fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
428 let param = func.append_param(job.body, func[value].ty);
429 for block in func.blocks().collect::<Vec<_>>() {
430 let Some(term) = func.terminator(block) else { continue };
431 let carry = if block == job.header {
432 value
433 } else if block == copy {
434 arrived
435 } else {
436 param
437 };
438 for at in func.target_list(term).iter() {
439 let call = func[at];
440 if call.block != job.body {
441 continue;
442 }
443 let args = func.append_arg(call.args, carry);
444 func.set_block_call(at, BlockCall { block: call.block, args });
445 }
446 }
447 for block in func.blocks().collect::<Vec<_>>() {
451 if block == job.header || block == copy {
452 continue;
453 }
454 for inst in func.insts(block).collect::<Vec<_>>() {
455 let swap = |had: Value| if had == value { param } else { had };
456 func.rewrite(func[inst].args, swap);
457 for at in func.target_list(inst).iter() {
458 func.rewrite(func[at].args, swap);
459 }
460 }
461 }
462}
463
464fn settle(func: &mut Func, an: &mut Analyses, copy: Block, stats: &mut Stats) -> bool {
477 let Some(term) = func.terminator(copy) else { return false };
478 let cond = func[func[term].args][0];
479 let answer = {
480 let cfg = an.cfg(func);
481 let dom = an.dominators(func);
482 let mut ranges = Ranges::new(func, cfg, dom);
483 prune::settled(func, &mut ranges, copy, cond)
484 };
485 let Some(taken) = answer else {
486 stats.missed(UNDECIDED);
487 return false;
488 };
489 let calls: Vec<BlockCall> = func.successors(term).collect();
490 let call = if taken { calls[0] } else { calls[1] };
491 simplify_cfg::jump_to(func, term, call);
492 stats.optimized(if taken { ENTERED } else { SKIPPED });
493 true
494}
495
496#[cfg(test)]
497mod tests {
498 use rucc_base::Interner;
499 use rucc_ir::{
500 Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
501 Signature, Type, verify_func,
502 };
503 use rucc_target::{TargetInfo, Triple};
504
505 use super::{HeaderCopy, SIZE, SPEED};
506 use crate::canon::Canon;
507 use crate::cfg::Cfg;
508 use crate::dom::Dominators;
509 use crate::loops::Loops;
510 use crate::stats::Kind;
511 use crate::{Fuel, Pass, Stats};
512
513 fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
519 let mut an = crate::machine::fixtures::analyses();
520 Canon.run(func, &mut an, &mut Fuel::unlimited());
521 pass.run(func, &mut an, &mut Fuel::unlimited())
522 }
523
524 fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
526 let cfg = Cfg::new(func);
527 let dom = Dominators::new(&cfg);
528 let loops = Loops::new(&cfg, &dom);
529 (cfg, dom, loops)
530 }
531
532 fn sound(func: &Func, names: &mut Interner) {
538 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
539 let module = Module::new(names.intern("t.c"), &target);
540 if let Err(errors) = verify_func(&module, func, names) {
541 panic!("{errors:#?}");
542 }
543 }
544
545 fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
557 let mut names = Interner::new();
558 let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
559 let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
560 let mut func = Func::new(names.intern("f"), signature);
561 let entry = func.create_block();
562 let head = func.create_block();
563 let body = func.create_block();
564 let done = func.create_block();
565 let limit = match bound {
566 Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
567 None => func.append_param(entry, Type::int(32)),
568 };
569 let i = func.append_param(head, Type::int(32));
570 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
571 Builder::new(&mut func, entry).jump(head, &[zero]);
572 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
573 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
574 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
575 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
576 Builder::new(&mut func, body).jump(head, &[next]);
577 Builder::new(&mut func, done).ret(&[i]);
578 (func, names, vec![entry, head, body, done])
579 }
580
581 fn tests_at_the_top(func: &Func) -> bool {
583 let (cfg, dom, loops) = forest(func);
584 let _ = dom;
585 let id = loops.all().next().expect("there is a loop");
586 let header = loops.header(id);
587 cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
588 }
589
590 #[test]
591 fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
592 let (mut func, mut names, _) = counted(None);
593 assert!(tests_at_the_top(&func), "the shape this pass is for");
594
595 let stats = copied(&mut func, &SPEED);
596 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
597 assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
598 sound(&func, &mut names);
599 }
600
601 #[test]
602 fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
603 let (mut func, mut names, blocks) = counted(None);
604 let body = blocks[2];
605 assert!(func[body].params.is_empty(), "the body carries nothing to start with");
606
607 copied(&mut func, &SPEED);
608 assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
609 assert_eq!(
610 Cfg::new(&func).predecessors(body).len(),
611 2,
612 "one edge from the header and one from the copy"
613 );
614 sound(&func, &mut names);
615 }
616
617 #[test]
618 fn an_entry_test_the_ranges_settle_is_taken_out() {
619 let (mut func, mut names, _) = counted(Some(10));
620
621 let stats = copied(&mut func, &SPEED);
622 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
623 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
624 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
625 sound(&func, &mut names);
626
627 let (cfg, _dom, loops) = forest(&func);
628 let id = loops.all().next().expect("the loop is still there");
629 let entry = func.entry().expect("there is an entry");
630 assert!(cfg.reaches(loops.header(id)), "and it is still reached");
631 assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
632 }
633
634 #[test]
635 fn a_loop_the_ranges_say_never_runs_is_removed() {
636 let (mut func, mut names, blocks) = counted(Some(0));
637
638 let stats = copied(&mut func, &SPEED);
639 assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
640 sound(&func, &mut names);
641
642 let (_cfg, _dom, loops) = forest(&func);
643 assert_eq!(loops.count(), 0, "there is no loop left");
644 assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
645 }
646
647 #[test]
648 fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
649 let (mut func, _names, _) = counted(None);
650
651 let stats = copied(&mut func, &SPEED);
652 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
653 assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
654 assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
655 }
656
657 #[test]
658 fn a_second_run_changes_nothing() {
659 let (mut func, mut names, _) = counted(None);
660 copied(&mut func, &SPEED);
661 let again =
662 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
663 assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
664 assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
665 sound(&func, &mut names);
666 }
667
668 #[test]
669 fn a_header_that_writes_to_memory_is_left_alone() {
670 let mut names = Interner::new();
671 let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
672 let mut func = Func::new(names.intern("f"), signature);
673 let entry = func.create_block();
674 let head = func.create_block();
675 let body = func.create_block();
676 let done = func.create_block();
677 let limit = func.append_param(entry, Type::int(32));
678 let addr = func.append_param(entry, Type::PTR);
679 let i = func.append_param(head, Type::int(32));
680 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
681 Builder::new(&mut func, entry).jump(head, &[zero]);
682 let access = MemInfo {
683 size: 4,
684 align: 4,
685 order: MemOrder::NotAtomic,
686 tbaa: None,
687 owns: 0,
688 restrict: Restrict::NONE,
689 };
690 Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
691 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
692 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
693 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
694 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
695 Builder::new(&mut func, body).jump(head, &[next]);
696 Builder::new(&mut func, done).ret(&[]);
697
698 let stats = copied(&mut func, &SPEED);
699 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
700 assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
701 assert!(tests_at_the_top(&func), "the loop is exactly as it was");
702 }
703
704 #[test]
705 fn a_header_larger_than_the_level_allows_is_left_alone() {
706 let stats = copied(&mut padded(6), &SIZE);
709 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
710 assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
711
712 let stats = copied(&mut padded(6), &SPEED);
713 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
714 }
715
716 fn padded(extra: usize) -> Func {
718 let (mut func, _names, blocks) = counted(None);
719 let head = blocks[1];
720 let term = func.terminator(head).expect("the header branches");
721 for _ in 0..extra {
722 let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
723 let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
724 func.remove_inst(inst);
725 func.insert_before(inst, term);
726 }
727 func
728 }
729
730 #[test]
731 fn fuel_stops_the_copy_where_it_stands() {
732 let (mut func, _names, _) = counted(None);
733 let mut an = crate::machine::fixtures::analyses();
734 Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
735
736 let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
737 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
738 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
739 assert!(tests_at_the_top(&func), "and the loop is as it was");
740 }
741
742 #[test]
743 fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
744 let (mut func, _names, _) = counted(None);
749
750 let stats =
751 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
752 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
753 assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
754 assert!(tests_at_the_top(&func), "and the loop is as it was");
755 }
756
757 #[test]
758 fn a_loop_with_no_preheader_is_declined() {
759 let mut names = Interner::new();
760 let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
761 let mut func = Func::new(names.intern("f"), signature);
762 let entry = func.create_block();
763 let one = func.create_block();
764 let two = func.create_block();
765 let head = func.create_block();
766 let body = func.create_block();
767 let done = func.create_block();
768 let c = func.append_param(entry, Type::int(1));
769 let limit = func.append_param(entry, Type::int(32));
770 let i = func.append_param(head, Type::int(32));
771 Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
772 let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
773 Builder::new(&mut func, one).jump(head, &[zero]);
774 let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
775 Builder::new(&mut func, two).jump(head, &[start]);
776 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
777 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
778 Builder::new(&mut func, body).jump(head, &[i]);
779 Builder::new(&mut func, done).ret(&[]);
780
781 let stats =
782 SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
783 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
784 assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
785
786 let stats = copied(&mut func, &SPEED);
788 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
789 sound(&func, &mut names);
790 }
791}