1use std::collections::{HashMap, HashSet};
152
153use rucc_base::Idx;
154use rucc_cost::heuristics;
155use rucc_ir::{Block, BlockCall, Builder, Def, Func, Inst, Opcode, Start, Value, ValueList};
156
157use crate::frontier::Frontiers;
158use crate::header_copy::{clone_into, repeatable};
159use crate::simplify_cfg::{Bindings, Edges, incoming, sweep, taken};
160use crate::{Analyses, Cfg, Dominators, Fuel, Loops, Pass, Preserved, Stats, uses};
161
162const THREADED: &str =
164 "edge pointed straight at the arm of the branch it arrives at that it decides";
165
166const NO_FUEL: &str = "edge left on a branch it decides, the pass ran out of fuel";
168
169const WOULD_COPY_EFFECT: &str =
171 "edge decides the branch it arrives at, but something in the block has to happen on the way";
172
173const WOULD_COPY_READ_BELOW: &str =
175 "edge decides the branch it arrives at, but a block below reads a value this one defines";
176
177const WOULD_COPY_CARRIED: &str =
179 "edge decides the branch it arrives at, but the arm carries a value the block works out";
180
181const WOULD_BREAK_A_LOOP: &str =
183 "edge decides the branch it arrives at, but threading it would give a loop a second way in";
184
185const COPIED: &str =
187 "edge pointed at a copy of the block it arrives at that goes straight to the arm it decides";
188
189const ODD: &str =
191 "edge decides the branch it arrives at, but the block has something in it that is not copied";
192
193const TOO_BIG: &str =
195 "edge decides the branch it arrives at, but the block is larger than a thread may copy";
196
197const TOO_LONG: &str =
199 "edge decides the branch it arrives at, but the path of copies it is on would be too long";
200
201const TOO_MANY: &str =
203 "edge decides the branch it arrives at, but this function has had its 64 copies";
204
205#[derive(Debug)]
207pub struct Thread {
208 name: &'static str,
210 budget: u32,
212}
213
214pub static FREE: Thread = Thread { name: "thread", budget: 0 };
216
217pub static COPY: Thread =
219 Thread { name: "thread-copy", budget: heuristics::JUMP_THREAD_DUPLICATION_INSNS };
220
221impl Pass for Thread {
222 fn name(&self) -> &'static str {
223 self.name
224 }
225
226 fn describe(&self) -> &'static str {
227 if self.budget == 0 {
228 "an edge that already decides the branch it arrives at is pointed at the arm that \
229 branch would have taken"
230 } else {
231 "an edge that already decides the branch it arrives at is pointed at the arm that \
232 branch would have taken, through a copy of the block if the block's values are read"
233 }
234 }
235
236 fn preserves(&self) -> Preserved {
237 Preserved::NONE
240 }
241
242 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
243 let mut stats = Stats::new();
244 let Some(entry) = func.entry() else { return stats };
245 let mut edges: Edges = incoming(func);
250 let mut leaks = leaky(func);
251 let unbound = Bindings::new();
252 let mut threaded = false;
253 let mut paths: HashMap<Block, u32> = HashMap::new();
256 let mut copies = 0;
257 'blocks: for block in func.blocks().collect::<Vec<Block>>() {
258 if block == entry || func[block].params.is_empty() {
259 continue;
260 }
261 let Some(term) = func.terminator(block) else { continue };
262 if !matches!(func[term].opcode, Opcode::BrIf | Opcode::Switch) {
263 continue;
264 }
265 if taken(func, term, &unbound).is_some() {
269 continue;
270 }
271 let effect = !skippable(func, block);
274 for (from, at) in edges.get(&block).cloned().unwrap_or_default() {
275 if from == block {
279 continue;
280 }
281 let subst = bind(func, block, at);
282 let Some(call) = taken(func, term, &subst) else { continue };
283 if call.block == block {
284 continue;
285 }
286 if effect {
287 stats.missed(WOULD_COPY_EFFECT);
288 continue;
289 }
290 let (free, why) = if leaks.contains(&block) {
293 (None, WOULD_COPY_READ_BELOW)
294 } else {
295 (carried(func, block, call, &subst), WOULD_COPY_CARRIED)
296 };
297 let path = if free.is_some() {
298 0
299 } else {
300 match self.path(func, an.loops(func), block, from, call.block, &paths, copies) {
301 Ok(path) => path,
302 Err(reason) => {
303 stats.missed(reason.unwrap_or(why));
304 continue;
305 }
306 }
307 };
308 if !allowed(an.loops(func), from, call.block) {
309 stats.missed(WOULD_BREAK_A_LOOP);
310 continue;
311 }
312 if !fuel.take() {
313 stats.missed(NO_FUEL);
317 break 'blocks;
318 }
319 if let Some(list) = edges.get_mut(&block) {
323 list.retain(|&(_, slot)| slot != at);
324 }
325 if let Some(args) = free {
326 let args = func.push_values(&args);
327 func.set_block_call(at, BlockCall { args, ..call });
328 edges.entry(call.block).or_default().push((from, at));
329 stats.optimized(THREADED);
330 } else {
331 let (copy, out) = copy(func, block, at, call, &subst);
332 edges.entry(call.block).or_default().push((copy, out));
333 paths.insert(copy, path);
334 copies += 1;
335 leaks = leaky(func);
339 stats.optimized(COPIED);
340 }
341 an.clear();
344 threaded = true;
345 }
346 }
347 if threaded {
348 sweep(func, an, &mut stats);
353 }
354 stats
355 }
356}
357
358impl Thread {
359 #[allow(clippy::too_many_arguments)]
366 fn path(
367 &self,
368 func: &Func,
369 loops: &Loops,
370 block: Block,
371 from: Block,
372 into: Block,
373 paths: &HashMap<Block, u32>,
374 copies: u32,
375 ) -> Result<u32, Option<&'static str>> {
376 if self.budget == 0 {
377 return Err(None);
378 }
379 let body: Vec<Inst> = func.insts(block).filter(|&inst| !func.is_terminator(inst)).collect();
380 if !body.iter().all(|&inst| repeatable(func, inst)) {
381 return Err(Some(ODD));
382 }
383 let mut cost = u32::try_from(body.len()).unwrap_or(u32::MAX);
384 if back_edge(loops, block, into) {
385 cost = cost.saturating_mul(heuristics::JUMP_THREAD_BACK_EDGE_SCALE);
386 }
387 if cost > self.budget {
388 return Err(Some(TOO_BIG));
389 }
390 let path = paths.get(&from).copied().unwrap_or(0).saturating_add(cost);
391 if path > heuristics::JUMP_THREAD_PATH_INSNS {
392 return Err(Some(TOO_LONG));
393 }
394 if copies >= heuristics::JUMP_THREAD_PATHS {
395 return Err(Some(TOO_MANY));
396 }
397 Ok(path)
398 }
399}
400
401fn back_edge(loops: &Loops, from: Block, into: Block) -> bool {
403 let mut id = loops.innermost(from);
404 while let Some(loop_id) = id {
405 if loops.header(loop_id) == into {
406 return true;
407 }
408 id = loops.parent(loop_id);
409 }
410 false
411}
412
413fn copy(
418 func: &mut Func,
419 block: Block,
420 at: Idx<BlockCall>,
421 call: BlockCall,
422 subst: &Bindings,
423) -> (Block, Idx<BlockCall>) {
424 let term = func.terminator(block).expect("the block was chosen for its terminator");
425 let mut map = subst.clone();
426 let copy = func.create_block();
427 let insts: Vec<Inst> = func.insts(block).filter(|&inst| inst != term).collect();
428 for inst in insts {
429 clone_into(func, copy, inst, &mut map);
430 }
431 let args: Vec<Value> =
432 func[call.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
433 let jump = Builder::new(func, copy).jump(call.block, &args);
434 let out = func.target_list(jump).iter().next().expect("a jump has one edge");
435 let edge = func[at];
436 func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY, ..edge });
437 repair(func, block, copy, &map);
438 (copy, out)
439}
440
441fn repair(func: &mut Func, block: Block, copy: Block, map: &Bindings) {
447 let values = read_outside(func, block, copy);
448 if values.is_empty() {
449 return;
450 }
451 let cfg = Cfg::new(func);
452 let dom = Dominators::new(&cfg);
453 let frontiers = Frontiers::new(&cfg, &dom);
454 let mut joins: HashSet<Block> = HashSet::new();
455 let mut work = vec![block, copy];
456 while let Some(at) = work.pop() {
457 for &join in frontiers.of(at) {
458 if joins.insert(join) {
459 work.push(join);
460 }
461 }
462 }
463 for value in values {
464 let copied = map.get(&value).copied().expect("the copy defines every value the block does");
465 let mut reaching = Reaching {
466 dom: &dom,
467 block,
468 copy,
469 value,
470 copied,
471 params: HashMap::new(),
472 memo: HashMap::new(),
473 };
474 merge(func, &cfg, &joins, &mut reaching);
475 }
476}
477
478fn read_outside(func: &Func, block: Block, copy: Block) -> Vec<Value> {
480 let mut seen = HashSet::new();
481 let mut out = Vec::new();
482 for other in func.blocks() {
483 if other == block || other == copy {
484 continue;
485 }
486 for inst in func.insts(other) {
487 uses::operands(func, inst, |value| {
488 if defined_in(func, value) == Some(block) && seen.insert(value) {
489 out.push(value);
490 }
491 });
492 }
493 }
494 out
495}
496
497struct Reaching<'a> {
499 dom: &'a Dominators,
501 block: Block,
503 copy: Block,
505 value: Value,
507 copied: Value,
509 params: HashMap<Block, Value>,
511 memo: HashMap<Block, Value>,
513}
514
515impl Reaching<'_> {
516 fn start(&mut self, of: Block) -> Value {
523 let mut chain = Vec::new();
524 let mut at = of;
525 let found = loop {
526 if let Some(¶m) = self.params.get(&at) {
527 break param;
528 }
529 if let Some(&known) = self.memo.get(&at) {
530 break known;
531 }
532 chain.push(at);
533 match self.dom.immediate_dominator(at) {
534 Some(up) if up == self.block => break self.value,
535 Some(up) if up == self.copy => break self.copied,
536 Some(up) => at = up,
537 None => break self.value,
538 }
539 };
540 for at in chain {
541 self.memo.insert(at, found);
542 }
543 found
544 }
545
546 fn end(&mut self, of: Block) -> Value {
548 if of == self.block {
549 self.value
550 } else if of == self.copy {
551 self.copied
552 } else {
553 self.start(of)
554 }
555 }
556}
557
558fn merge(func: &mut Func, cfg: &Cfg, joins: &HashSet<Block>, reaching: &mut Reaching<'_>) {
561 let (block, copy, value) = (reaching.block, reaching.copy, reaching.value);
562 let mut readers = Vec::new();
565 for other in func.blocks() {
566 if other == block || other == copy {
567 continue;
568 }
569 let mut reads = false;
570 for inst in func.insts(other) {
571 uses::operands(func, inst, |used| reads |= used == value);
572 }
573 if reads {
574 readers.push(other);
575 }
576 }
577 let mut live: HashSet<Block> = readers.iter().copied().collect();
578 let mut work = readers.clone();
579 while let Some(at) = work.pop() {
580 for &pred in cfg.predecessors(at) {
581 if pred != block && pred != copy && live.insert(pred) {
582 work.push(pred);
583 }
584 }
585 }
586 let mut places: Vec<Block> = joins
587 .iter()
588 .copied()
589 .filter(|&join| join != block && join != copy && live.contains(&join))
590 .collect();
591 places.sort_by_key(|join| join.index());
592 let ty = func[value].ty;
593 let decls: Vec<u32> = func.value_decls(value).collect();
594 for &place in &places {
595 let param = func.append_param(place, ty);
596 for &decl in &decls {
597 func.declare_value(param, decl);
598 }
599 reaching.params.insert(place, param);
600 }
601 for &reader in &readers {
602 let now = reaching.start(reader);
603 if now == value {
604 continue;
605 }
606 let swap = |had: Value| if had == value { now } else { had };
607 for inst in func.insts(reader).collect::<Vec<Inst>>() {
608 func.rewrite(func[inst].args, swap);
609 for at in func.target_list(inst).iter() {
610 func.rewrite(func[at].args, swap);
611 }
612 }
613 }
614 for other in func.blocks().collect::<Vec<Block>>() {
617 let Some(term) = func.terminator(other) else { continue };
618 for at in func.target_list(term).iter() {
619 let call = func[at];
620 if !reaching.params.contains_key(&call.block) {
621 continue;
622 }
623 let carry = reaching.end(other);
624 let args = func.append_arg(call.args, carry);
625 func.set_block_call(at, BlockCall { args, ..call });
626 }
627 }
628 let starts: Vec<(Start, Value)> = func
631 .value_starts(value)
632 .filter_map(|start| {
633 let at = func.start_place(start).map_or(start.block, |(at, _)| at);
634 if at == block || at == copy {
635 return None;
636 }
637 let now = reaching.start(at);
638 (now != value).then_some((start, now))
639 })
640 .collect();
641 let mut targets: Vec<Value> = starts.iter().map(|&(_, now)| now).collect();
642 targets.dedup();
643 for target in targets {
644 let which: Vec<Start> =
645 starts.iter().filter(|&&(_, now)| now == target).map(|&(start, _)| start).collect();
646 if !which.is_empty() {
647 func.move_starts(value, target, &which);
648 }
649 }
650}
651
652fn bind(func: &Func, block: Block, at: Idx<BlockCall>) -> Bindings {
654 let args = func[at].args;
655 let params = func[block].params.iter().copied();
656 params.zip(func[args].iter().copied()).collect()
657}
658
659fn carried(func: &Func, block: Block, call: BlockCall, subst: &Bindings) -> Option<Vec<Value>> {
671 let mut out = Vec::with_capacity(func[call.args].len());
672 for &arg in &func[call.args] {
673 if let Some(&bound) = subst.get(&arg) {
674 out.push(bound);
675 continue;
676 }
677 if let Def::Result { inst, .. } = func[arg].def {
678 if func.block_of(inst) == Some(block) {
679 return None;
680 }
681 }
682 out.push(arg);
683 }
684 Some(out)
685}
686
687fn skippable(func: &Func, block: Block) -> bool {
698 func.insts(block).all(|inst| func.is_terminator(inst) || !func[inst].opcode.has_effects())
699}
700
701fn leaky(func: &Func) -> HashSet<Block> {
714 let mut out = HashSet::new();
715 for block in func.blocks().collect::<Vec<Block>>() {
716 for inst in func.insts(block).collect::<Vec<Inst>>() {
717 uses::operands(func, inst, |value| {
718 if let Some(home) = defined_in(func, value) {
719 if home != block {
720 out.insert(home);
721 }
722 }
723 });
724 }
725 }
726 out
727}
728
729fn defined_in(func: &Func, value: Value) -> Option<Block> {
731 match func[value].def {
732 Def::Result { inst, .. } => func.block_of(inst),
733 Def::Param { block, .. } => Some(block),
734 }
735}
736
737fn allowed(loops: &Loops, from: Block, into: Block) -> bool {
744 if loops.is_irreducible(from) || loops.is_irreducible(into) {
745 return false;
746 }
747 if loops.all().any(|id| loops.latches(id).contains(&from)) {
748 return false;
749 }
750 let mut id = loops.innermost(into);
751 while let Some(loop_id) = id {
752 if !loops.contains(loop_id, from) && loops.header(loop_id) != into {
755 return false;
756 }
757 id = loops.parent(loop_id);
758 }
759 true
760}
761
762#[cfg(test)]
763mod tests {
764 use rucc_base::Interner;
765 use rucc_ir::{
766 Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Restrict, Signature, Type, Value,
767 };
768
769 use std::collections::HashMap;
770
771 use rucc_ir::{Module, verify_func};
772 use rucc_target::{TargetInfo, Triple};
773
774 use super::{COPY, FREE, Thread};
775 use crate::stats::Kind;
776 use crate::{Fuel, Pass, Stats};
777
778 fn thread(func: &mut Func) -> Stats {
780 FREE.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
781 }
782
783 fn copying(func: &mut Func) -> Stats {
785 copying_with(©, func)
786 }
787
788 fn copying_with(pass: &Thread, func: &mut Func) -> Stats {
789 let stats =
790 pass.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
791 let mut names = Interner::new();
792 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
793 let module = Module::new(names.intern("t.c"), &target);
794 if let Err(errors) = verify_func(&module, func, &names) {
795 panic!("{errors:#?}");
796 }
797 stats
798 }
799
800 fn blocks(func: &Func) -> Vec<usize> {
802 func.blocks().map(Block::index).collect()
803 }
804
805 fn goes_to(func: &Func, block: usize) -> Vec<usize> {
807 let block = Block::from_usize(block);
808 let term = func.terminator(block).expect("every block here has one");
809 func.successors(term).map(|call| call.block.index()).collect()
810 }
811
812 fn carries(func: &Func, block: usize) -> Vec<Value> {
814 let block = Block::from_usize(block);
815 let term = func.terminator(block).expect("every block here has one");
816 let call = func.successors(term).next().expect("a terminator here has an edge");
817 func[call.args].to_vec()
818 }
819
820 fn diamond(left: i128, right: i128) -> (Func, [Value; 2]) {
827 let mut names = Interner::new();
828 let mut func = Func::new(names.intern("f"), Signature::new());
829 let entry = func.create_block();
830 let arms = [func.create_block(), func.create_block()];
831 let join = func.create_block();
832 let param = func.append_param(join, Type::int(32));
833 let yes = func.create_block();
834 let no = func.create_block();
835
836 let mut build = Builder::new(&mut func, entry);
837 let cond = build.iconst(Type::int(1), 1);
838 build.br_if(cond, arms[0], &[], arms[1], &[]);
839 let mut sent = Vec::new();
840 for (arm, value) in arms.iter().zip([left, right]) {
841 let mut build = Builder::new(&mut func, *arm);
842 let it = build.iconst(Type::int(32), value);
843 sent.push(it);
844 build.jump(join, &[it]);
845 }
846 let mut build = Builder::new(&mut func, join);
847 let one = build.iconst(Type::int(32), 1);
848 let test = build.icmp(IntPred::Eq, param, one);
849 build.br_if(test, yes, &[], no, &[]);
850 for block in [yes, no] {
851 let mut build = Builder::new(&mut func, block);
852 build.ret(&[]);
853 }
854 (func, [sent[0], sent[1]])
855 }
856
857 #[test]
858 fn both_edges_of_a_join_that_decides_its_test_are_threaded() {
859 let (mut func, _) = diamond(1, 2);
860 let stats = thread(&mut func);
861 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 2);
862 assert_eq!(goes_to(&func, 1), vec![4]);
865 assert_eq!(goes_to(&func, 2), vec![5]);
866 assert_eq!(blocks(&func), vec![0, 1, 2, 4, 5]);
870 assert_eq!(stats.count(Kind::Optimized, crate::simplify_cfg::REMOVED), 1);
871 }
872
873 #[test]
874 fn an_edge_that_does_not_decide_the_test_is_left_alone() {
875 let mut names = Interner::new();
876 let signature = Signature::new().with_params(&[Type::int(32)]);
877 let mut func = Func::new(names.intern("f"), signature);
878 let entry = func.create_block();
879 let outside = func.append_param(entry, Type::int(32));
882 let join = func.create_block();
883 let param = func.append_param(join, Type::int(32));
884 let yes = func.create_block();
885 let no = func.create_block();
886
887 let mut build = Builder::new(&mut func, entry);
888 build.jump(join, &[outside]);
889 let mut build = Builder::new(&mut func, join);
890 let one = build.iconst(Type::int(32), 1);
891 let test = build.icmp(IntPred::Eq, param, one);
892 build.br_if(test, yes, &[], no, &[]);
893 for block in [yes, no] {
894 let mut build = Builder::new(&mut func, block);
895 build.ret(&[]);
896 }
897
898 let stats = thread(&mut func);
899 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
900 assert_eq!(goes_to(&func, 0), vec![1]);
901 }
902
903 #[test]
904 fn a_branch_decided_whichever_way_control_arrived_is_left_to_simplify_cfg() {
905 let mut names = Interner::new();
906 let mut func = Func::new(names.intern("f"), Signature::new());
907 let entry = func.create_block();
908 let arms = [func.create_block(), func.create_block()];
909 let join = func.create_block();
910 func.append_param(join, Type::int(32));
911 let yes = func.create_block();
912 let no = func.create_block();
913
914 let mut build = Builder::new(&mut func, entry);
915 let cond = build.iconst(Type::int(1), 1);
916 build.br_if(cond, arms[0], &[], arms[1], &[]);
917 for (arm, value) in arms.iter().zip([1, 2]) {
918 let mut build = Builder::new(&mut func, *arm);
919 let it = build.iconst(Type::int(32), value);
920 build.jump(join, &[it]);
921 }
922 let mut build = Builder::new(&mut func, join);
923 let known = build.iconst(Type::int(1), 1);
927 build.br_if(known, yes, &[], no, &[]);
928 for block in [yes, no] {
929 let mut build = Builder::new(&mut func, block);
930 build.ret(&[]);
931 }
932
933 let stats = thread(&mut func);
934 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
935 assert_eq!(goes_to(&func, 1), vec![3]);
936 assert_eq!(goes_to(&func, 2), vec![3]);
937 }
938
939 #[test]
940 fn a_block_with_something_that_happens_in_it_needs_the_copy() {
941 let mut names = Interner::new();
942 let mut func = Func::new(names.intern("f"), Signature::new());
943 let entry = func.create_block();
944 let arms = [func.create_block(), func.create_block()];
945 let join = func.create_block();
946 let param = func.append_param(join, Type::int(32));
947 let yes = func.create_block();
948 let no = func.create_block();
949
950 let mut build = Builder::new(&mut func, entry);
951 let cond = build.iconst(Type::int(1), 1);
952 build.br_if(cond, arms[0], &[], arms[1], &[]);
953 for (arm, value) in arms.iter().zip([1, 2]) {
954 let mut build = Builder::new(&mut func, *arm);
955 let it = build.iconst(Type::int(32), value);
956 build.jump(join, &[it]);
957 }
958 let mut build = Builder::new(&mut func, join);
959 let what = build.iconst(Type::int(32), 7);
962 let address = build.iconst(Type::int(64), 16);
963 let address = build.unary(rucc_ir::Opcode::IntToPtr, address, Type::PTR);
964 let info = MemInfo {
965 size: 4,
966 align: 4,
967 order: MemOrder::NotAtomic,
968 tbaa: None,
969 owns: 0,
970 restrict: Restrict::NONE,
971 };
972 build.store(what, address, info, Flags::NONE);
973 let one = build.iconst(Type::int(32), 1);
974 let test = build.icmp(IntPred::Eq, param, one);
975 build.br_if(test, yes, &[], no, &[]);
976 for block in [yes, no] {
977 let mut build = Builder::new(&mut func, block);
978 build.ret(&[]);
979 }
980
981 let stats = thread(&mut func);
982 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
983 assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_EFFECT), 2);
984 }
985
986 fn clamp() -> Func {
993 clamp_with(|_, _| {})
994 }
995
996 fn clamp_with(extra: impl FnOnce(&mut Builder<'_>, Value)) -> Func {
998 let mut names = Interner::new();
999 let signature = Signature::new().with_returns(&[Type::int(32)]);
1000 let mut func = Func::new(names.intern("f"), signature);
1001 let entry = func.create_block();
1002 let arms = [func.create_block(), func.create_block()];
1003 let join = func.create_block();
1004 let param = func.append_param(join, Type::int(32));
1005 let yes = func.create_block();
1006 let no = func.create_block();
1007
1008 let mut build = Builder::new(&mut func, entry);
1009 let cond = build.iconst(Type::int(1), 1);
1010 build.br_if(cond, arms[0], &[], arms[1], &[]);
1011 for (arm, value) in arms.iter().zip([1, 2]) {
1012 let mut build = Builder::new(&mut func, *arm);
1013 let it = build.iconst(Type::int(32), value);
1014 build.jump(join, &[it]);
1015 }
1016 let mut build = Builder::new(&mut func, join);
1017 extra(&mut build, param);
1018 let one = build.iconst(Type::int(32), 1);
1019 let test = build.icmp(IntPred::Eq, param, one);
1020 build.br_if(test, yes, &[], no, &[]);
1021 let mut build = Builder::new(&mut func, yes);
1022 let floor = build.iconst(Type::int(32), 15);
1023 build.ret(&[floor]);
1024 let mut build = Builder::new(&mut func, no);
1027 build.ret(&[param]);
1028 func
1029 }
1030
1031 #[test]
1032 fn a_value_the_block_defines_and_something_below_it_reads_needs_the_copy() {
1033 let mut func = clamp();
1034 let stats = thread(&mut func);
1035 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
1036 assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_READ_BELOW), 2);
1037 assert_eq!(goes_to(&func, 1), vec![3]);
1038 assert_eq!(goes_to(&func, 2), vec![3]);
1039 }
1040
1041 #[test]
1044 fn a_copy_threads_the_clamp_and_the_read_below_gets_a_merge() {
1045 let mut func = clamp();
1046 let stats = copying(&mut func);
1047 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2, "{stats:?}");
1048 assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_READ_BELOW), 0);
1049 assert!(!blocks(&func).contains(&3), "{:?}", blocks(&func));
1052 let first = goes_to(&func, 1)[0];
1053 let second = goes_to(&func, 2)[0];
1054 assert_eq!(goes_to(&func, first), vec![4]);
1055 assert_eq!(goes_to(&func, second), vec![5]);
1056 let returned = Block::from_usize(5);
1060 let term = func.terminator(returned).expect("a return");
1061 let read = func[func[term].args][0];
1062 assert_eq!(super::defined_in(&func, read), Some(Block::from_usize(2)));
1063 }
1064
1065 #[test]
1066 fn a_copy_is_not_made_of_a_block_with_something_that_happens_in_it() {
1067 let mut func = clamp_with(|build, _| {
1068 let what = build.iconst(Type::int(32), 7);
1069 let address = build.iconst(Type::int(64), 16);
1070 let address = build.unary(rucc_ir::Opcode::IntToPtr, address, Type::PTR);
1071 let info = MemInfo {
1072 size: 4,
1073 align: 4,
1074 order: MemOrder::NotAtomic,
1075 tbaa: None,
1076 owns: 0,
1077 restrict: Restrict::NONE,
1078 };
1079 build.store(what, address, info, Flags::NONE);
1080 });
1081 let stats = copying(&mut func);
1082 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
1083 assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_EFFECT), 2);
1084 }
1085
1086 #[test]
1089 fn a_block_larger_than_the_budget_is_not_copied() {
1090 for (adds, copied) in [(13, 2), (14, 0)] {
1091 let mut func = clamp_with(|build, param| {
1092 let mut sum = param;
1093 for _ in 0..adds {
1094 sum = build.binary(rucc_ir::Opcode::Add, sum, param, Flags::NONE);
1095 }
1096 });
1097 let stats = copying(&mut func);
1099 assert_eq!(stats.count(Kind::Optimized, super::COPIED), copied, "{adds}: {stats:?}");
1100 if copied == 0 {
1101 assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 2);
1102 }
1103 }
1104 }
1105
1106 #[test]
1107 fn a_path_of_copies_may_not_grow_past_its_limit() {
1108 let func = clamp();
1109 let an = crate::machine::fixtures::analyses();
1110 let join = Block::from_usize(3);
1111 let from = Block::from_usize(1);
1112 let into = Block::from_usize(5);
1113 let loops = an.loops(&func);
1114 let mut paths = HashMap::new();
1115 assert_eq!(COPY.path(&func, loops, join, from, into, &paths, 0), Ok(2));
1116 paths.insert(from, 99);
1117 assert_eq!(
1118 COPY.path(&func, loops, join, from, into, &paths, 0),
1119 Err(Some(super::TOO_LONG))
1120 );
1121 paths.insert(from, 98);
1122 assert_eq!(COPY.path(&func, loops, join, from, into, &paths, 0), Ok(100));
1123 assert_eq!(
1124 COPY.path(&func, loops, join, from, into, &paths, 64),
1125 Err(Some(super::TOO_MANY))
1126 );
1127 assert_eq!(FREE.path(&func, loops, join, from, into, &paths, 0), Err(None));
1128 }
1129
1130 #[test]
1132 fn one_run_makes_at_most_sixty_four_copies() {
1133 let mut names = Interner::new();
1134 let signature =
1135 Signature::new().with_params(&[Type::int(32)]).with_returns(&[Type::int(32)]);
1136 let mut func = Func::new(names.intern("f"), signature);
1137 let entry = func.create_block();
1138 let pick = func.append_param(entry, Type::int(32));
1139 let arms: Vec<Block> = (0..70).map(|_| func.create_block()).collect();
1140 let join = func.create_block();
1141 let param = func.append_param(join, Type::int(32));
1142 let yes = func.create_block();
1143 let no = func.create_block();
1144 let cases: Vec<(i128, Block)> = (0..).zip(arms.iter().copied()).collect();
1145 let (&default, _) = arms.split_last().expect("arms");
1146 Builder::new(&mut func, entry).switch(pick, default, &cases[..69]);
1147 for (&arm, value) in arms.iter().zip(0..) {
1148 let mut build = Builder::new(&mut func, arm);
1149 let it = build.iconst(Type::int(32), value);
1150 build.jump(join, &[it]);
1151 }
1152 let mut build = Builder::new(&mut func, join);
1153 let one = build.iconst(Type::int(32), 1);
1154 let test = build.icmp(IntPred::Eq, param, one);
1155 let sum = build.binary(rucc_ir::Opcode::Add, param, one, Flags::NONE);
1158 build.br_if(test, yes, &[], no, &[]);
1159 let mut build = Builder::new(&mut func, yes);
1160 let zero = build.iconst(Type::int(32), 0);
1161 build.ret(&[zero]);
1162 let mut build = Builder::new(&mut func, no);
1163 build.ret(&[sum]);
1164
1165 let stats = copying(&mut func);
1166 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 64, "{stats:?}");
1167 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1, "{stats:?}");
1171 assert_eq!(stats.count(Kind::Missed, super::TOO_MANY), 5, "{stats:?}");
1172 }
1173
1174 #[test]
1175 fn a_thread_back_to_a_loop_header_counts_each_instruction_twice() {
1176 let func = loop_with_a_parameter(2);
1177 let an = crate::machine::fixtures::analyses();
1178 let loops = an.loops(&func);
1179 let header = Block::from_usize(1);
1180 let body = Block::from_usize(2);
1181 assert!(super::back_edge(loops, body, header));
1182 assert!(!super::back_edge(loops, header, Block::from_usize(3)));
1183 }
1184
1185 #[test]
1186 fn an_arm_carrying_a_value_the_block_worked_out_is_threaded_through_a_copy() {
1187 let mut names = Interner::new();
1188 let signature = Signature::new().with_returns(&[Type::int(32)]);
1189 let mut func = Func::new(names.intern("f"), signature);
1190 let entry = func.create_block();
1191 let arms = [func.create_block(), func.create_block()];
1192 let join = func.create_block();
1193 let param = func.append_param(join, Type::int(32));
1194 let yes = func.create_block();
1195 let got = func.append_param(yes, Type::int(32));
1196 let no = func.create_block();
1197
1198 let mut build = Builder::new(&mut func, entry);
1199 let cond = build.iconst(Type::int(1), 1);
1200 build.br_if(cond, arms[0], &[], arms[1], &[]);
1201 for (arm, value) in arms.iter().zip([1, 2]) {
1202 let mut build = Builder::new(&mut func, *arm);
1203 let it = build.iconst(Type::int(32), value);
1204 build.jump(join, &[it]);
1205 }
1206 let mut build = Builder::new(&mut func, join);
1207 let one = build.iconst(Type::int(32), 1);
1208 let test = build.icmp(IntPred::Eq, param, one);
1209 let sum = build.binary(rucc_ir::Opcode::Add, param, one, Flags::NONE);
1210 build.br_if(test, yes, &[sum], no, &[]);
1211 let mut build = Builder::new(&mut func, yes);
1212 build.ret(&[got]);
1213 let mut build = Builder::new(&mut func, no);
1214 let zero = build.iconst(Type::int(32), 0);
1215 build.ret(&[zero]);
1216
1217 let stats = copying(&mut func);
1218 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1219 assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
1220 let copy = goes_to(&func, 1)[0];
1221 assert_eq!(goes_to(&func, copy), vec![4]);
1222 assert_eq!(carries(&func, copy).len(), 1);
1223 }
1224
1225 #[test]
1226 fn an_arm_carrying_a_value_the_block_worked_out_needs_the_copy() {
1227 let mut names = Interner::new();
1228 let mut func = Func::new(names.intern("f"), Signature::new());
1229 let entry = func.create_block();
1230 let arms = [func.create_block(), func.create_block()];
1231 let join = func.create_block();
1232 let param = func.append_param(join, Type::int(32));
1233 let yes = func.create_block();
1234 func.append_param(yes, Type::int(32));
1235 let no = func.create_block();
1236
1237 let mut build = Builder::new(&mut func, entry);
1238 let cond = build.iconst(Type::int(1), 1);
1239 build.br_if(cond, arms[0], &[], arms[1], &[]);
1240 for (arm, value) in arms.iter().zip([1, 2]) {
1241 let mut build = Builder::new(&mut func, *arm);
1242 let it = build.iconst(Type::int(32), value);
1243 build.jump(join, &[it]);
1244 }
1245 let mut build = Builder::new(&mut func, join);
1246 let one = build.iconst(Type::int(32), 1);
1247 let test = build.icmp(IntPred::Eq, param, one);
1248 let sum = build.binary(rucc_ir::Opcode::Add, param, one, Flags::NONE);
1251 build.br_if(test, yes, &[sum], no, &[]);
1252 for block in [yes, no] {
1253 let mut build = Builder::new(&mut func, block);
1254 build.ret(&[]);
1255 }
1256
1257 let stats = thread(&mut func);
1258 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1261 assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_CARRIED), 1);
1262 assert_eq!(goes_to(&func, 2), vec![5]);
1263 assert_eq!(goes_to(&func, 1), vec![3]);
1264 }
1265
1266 #[test]
1267 fn the_block_parameter_is_substituted_into_what_the_arm_carries() {
1268 let mut names = Interner::new();
1269 let mut func = Func::new(names.intern("f"), Signature::new());
1270 let entry = func.create_block();
1271 let arms = [func.create_block(), func.create_block()];
1272 let join = func.create_block();
1273 let param = func.append_param(join, Type::int(32));
1274 let yes = func.create_block();
1275 func.append_param(yes, Type::int(32));
1276 let no = func.create_block();
1277
1278 let mut build = Builder::new(&mut func, entry);
1279 let cond = build.iconst(Type::int(1), 1);
1280 build.br_if(cond, arms[0], &[], arms[1], &[]);
1281 let mut sent = Vec::new();
1282 for (arm, value) in arms.iter().zip([1, 2]) {
1283 let mut build = Builder::new(&mut func, *arm);
1284 let it = build.iconst(Type::int(32), value);
1285 sent.push(it);
1286 build.jump(join, &[it]);
1287 }
1288 let mut build = Builder::new(&mut func, join);
1289 let one = build.iconst(Type::int(32), 1);
1290 let test = build.icmp(IntPred::Eq, param, one);
1291 build.br_if(test, yes, &[param], no, &[]);
1294 for block in [yes, no] {
1295 let mut build = Builder::new(&mut func, block);
1296 build.ret(&[]);
1297 }
1298
1299 let stats = thread(&mut func);
1300 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 2);
1301 assert_eq!(goes_to(&func, 1), vec![4]);
1302 assert_eq!(carries(&func, 1), vec![sent[0]]);
1303 }
1304
1305 #[test]
1306 fn a_switch_the_edge_decides_is_threaded() {
1307 let mut names = Interner::new();
1308 let mut func = Func::new(names.intern("f"), Signature::new());
1309 let entry = func.create_block();
1310 let arms = [func.create_block(), func.create_block()];
1311 let join = func.create_block();
1312 let param = func.append_param(join, Type::int(32));
1313 let cases = [func.create_block(), func.create_block(), func.create_block()];
1314
1315 let mut build = Builder::new(&mut func, entry);
1316 let cond = build.iconst(Type::int(1), 1);
1317 build.br_if(cond, arms[0], &[], arms[1], &[]);
1318 for (arm, value) in arms.iter().zip([0, 1]) {
1319 let mut build = Builder::new(&mut func, *arm);
1320 let it = build.iconst(Type::int(32), value);
1321 build.jump(join, &[it]);
1322 }
1323 let mut build = Builder::new(&mut func, join);
1324 build.switch(param, cases[0], &[(0, cases[1]), (1, cases[2])]);
1325 for block in cases {
1326 let mut build = Builder::new(&mut func, block);
1327 build.ret(&[]);
1328 }
1329
1330 let stats = thread(&mut func);
1331 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 2);
1332 assert_eq!(goes_to(&func, 1), vec![cases[1].index()]);
1333 assert_eq!(goes_to(&func, 2), vec![cases[2].index()]);
1334 }
1335
1336 fn loop_with_a_parameter(arm: usize) -> Func {
1350 let mut names = Interner::new();
1351 let signature = Signature::new().with_params(&[Type::int(32)]);
1352 let mut func = Func::new(names.intern("f"), signature);
1353 let entry = func.create_block();
1354 let outside = func.append_param(entry, Type::int(32));
1355 let header = func.create_block();
1356 let param = func.append_param(header, Type::int(32));
1357 let body = func.create_block();
1358 let out = func.create_block();
1359 let elsewhere = func.create_block();
1360 let taken = [entry, header, body, out, elsewhere][arm];
1361
1362 let mut build = Builder::new(&mut func, entry);
1363 let one = build.iconst(Type::int(32), 1);
1364 build.jump(header, &[one]);
1365 let mut build = Builder::new(&mut func, header);
1366 let lit = build.iconst(Type::int(32), 1);
1367 let test = build.icmp(IntPred::Eq, param, lit);
1368 build.br_if(test, taken, &[], out, &[]);
1369 let mut build = Builder::new(&mut func, body);
1370 build.jump(header, &[outside]);
1373 for block in [out, elsewhere] {
1374 let mut build = Builder::new(&mut func, block);
1375 build.ret(&[]);
1376 }
1377 func
1378 }
1379
1380 #[test]
1381 fn threading_into_a_loop_anywhere_but_its_header_is_refused() {
1382 let mut func = loop_with_a_parameter(2);
1385 let stats = thread(&mut func);
1386 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
1387 assert_eq!(stats.count(Kind::Missed, super::WOULD_BREAK_A_LOOP), 1);
1388 assert_eq!(goes_to(&func, 0), vec![1]);
1389 }
1390
1391 #[test]
1392 fn threading_onto_a_block_outside_the_loop_is_allowed() {
1393 let mut func = loop_with_a_parameter(4);
1396 let stats = thread(&mut func);
1397 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1398 assert_eq!(goes_to(&func, 0), vec![4]);
1399 }
1400
1401 #[test]
1402 fn threading_onto_the_header_of_a_loop_is_allowed() {
1403 let mut names = Interner::new();
1404 let mut func = Func::new(names.intern("f"), Signature::new());
1405 let entry = func.create_block();
1406 let join = func.create_block();
1407 let param = func.append_param(join, Type::int(32));
1408 let header = func.create_block();
1409 let out = func.create_block();
1410
1411 let mut build = Builder::new(&mut func, entry);
1412 let one = build.iconst(Type::int(32), 1);
1413 build.jump(join, &[one]);
1414 let mut build = Builder::new(&mut func, join);
1415 let lit = build.iconst(Type::int(32), 1);
1416 let test = build.icmp(IntPred::Eq, param, lit);
1417 build.br_if(test, header, &[], out, &[]);
1418 let mut build = Builder::new(&mut func, header);
1419 let again = build.iconst(Type::int(1), 1);
1422 build.br_if(again, header, &[], out, &[]);
1423 let mut build = Builder::new(&mut func, out);
1424 build.ret(&[]);
1425
1426 let stats = thread(&mut func);
1427 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1428 assert_eq!(goes_to(&func, 0), vec![2]);
1429 }
1430
1431 #[test]
1432 fn fuel_stops_the_threading_where_it_stands() {
1433 let (mut func, _) = diamond(1, 2);
1434 let mut fuel = Fuel::of(1);
1435 let stats = FREE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
1436 assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1437 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1438 assert_eq!(goes_to(&func, 2), vec![3], "the second edge is where it was");
1439 }
1440}