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