1use std::collections::HashSet;
107
108use rucc_cost::heuristics;
109use rucc_ir::{Block, Flags, Func, Inst, Opcode, Value};
110
111use crate::cfg::Cfg;
112use crate::dom::{Dominators, PostDominators};
113use crate::live::Liveness;
114use crate::loops::{LoopId, Loops};
115use crate::machine::Machine;
116use crate::pressure::{Class, Pressure, class_of};
117use crate::range::query::Ranges;
118use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats, speculate};
119
120const HOISTED: &str = "computation moved in front of the loop, nothing in the loop changes it";
121const SPECULATIVE: &str =
122 "left in the loop, it does not run on every entry and working it out early could fault";
123const EFFECTS: &str = "left in the loop, moving it would change what the program does";
124const PRESSURE: &str = "left in the loop, it is cheaper than the register holding it would cost";
125const MEMORY: &str = "left in the loop, the loop writes memory and nothing here says which memory";
126const NO_PREHEADER: &str = "loop left as it was, it has not been canonicalized";
127const SPINS: &str = "loop left as it was, it has no way out, so nothing in it is known to run";
128const NO_FUEL: &str = "loop left as it was, the pass ran out of fuel";
129
130#[derive(Debug)]
132pub struct Licm;
133
134pub static LICM: Licm = Licm;
136
137impl Pass for Licm {
138 fn name(&self) -> &'static str {
139 "licm"
140 }
141
142 fn describe(&self) -> &'static str {
143 "moves a computation whose operands do not change in a loop in front of the loop"
144 }
145
146 fn preserves(&self) -> Preserved {
147 Preserved::ALL.without(Analysis::Liveness)
151 }
152
153 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
154 let mut stats = Stats::new();
155 if func.entry().is_none() {
156 return stats;
157 }
158 let machine = an.machine();
159 let cfg = an.cfg(func).clone();
160 let loops = an.loops(func).clone();
161 if loops.count() == 0 {
162 return stats;
163 }
164 let dom = an.dominators(func).clone();
165 let post = an.post_dominators(func).clone();
166 let invented: HashSet<Block> = post.fake_exits().iter().copied().collect();
167
168 let mut order: Vec<LoopId> = loops.all().collect();
172 order.sort_by_key(|&id| std::cmp::Reverse(loops.depth(id)));
173
174 let mut pressure = Pressure::of(func, &cfg, &Liveness::of(func, &cfg));
175 for id in order {
176 let job = Job {
177 machine,
178 cfg: &cfg,
179 dom: &dom,
180 post: &post,
181 loops: &loops,
182 invented: &invented,
183 };
184 if job.run(func, &pressure, id, fuel, &mut stats) {
185 pressure = Pressure::of(func, &cfg, &Liveness::of(func, &cfg));
189 }
190 }
191 stats
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197enum Move {
198 Anywhere,
200 IfItWasGoingToRun,
202 Nowhere,
204}
205
206struct Job<'a> {
208 machine: Machine,
209 cfg: &'a Cfg,
210 dom: &'a Dominators,
211 post: &'a PostDominators,
212 loops: &'a Loops,
213 invented: &'a HashSet<Block>,
214}
215
216impl Job<'_> {
217 fn run(
219 &self,
220 func: &mut Func,
221 pressure: &Pressure,
222 id: LoopId,
223 fuel: &mut Fuel,
224 stats: &mut Stats,
225 ) -> bool {
226 let Some(preheader) = self.loops.preheader(self.cfg, id) else {
227 stats.missed(NO_PREHEADER);
228 return false;
229 };
230 let Some(landing) = func.terminator(preheader) else {
231 return false;
232 };
233 let plan = self.plan(func, pressure, id, preheader, fuel, stats);
234 for inst in &plan {
235 func.remove_inst(*inst);
239 func.insert_before(*inst, landing);
240 stats.optimized(HOISTED);
241 }
242 !plan.is_empty()
243 }
244
245 fn plan(
256 &self,
257 func: &Func,
258 pressure: &Pressure,
259 id: LoopId,
260 preheader: Block,
261 fuel: &mut Fuel,
262 stats: &mut Stats,
263 ) -> Vec<Inst> {
264 let header = self.loops.header(id);
265 let inside: HashSet<Block> = self.loops.blocks(id).iter().copied().collect();
266 let spins = self.loops.blocks(id).iter().any(|block| self.invented.contains(block));
269 if spins {
270 stats.missed(SPINS);
271 }
272 let writes = self
274 .loops
275 .blocks(id)
276 .iter()
277 .any(|block| func.insts(*block).any(|inst| func[inst].opcode.writes_memory()));
278 let ends = self
279 .loops
280 .blocks(id)
281 .iter()
282 .any(|block| func.insts(*block).any(|inst| ends_a_lifetime(func, inst)));
283 let mut ranges = Ranges::new(func, self.cfg, self.dom);
284 let mut plan = Vec::new();
285 let mut passengers: HashSet<Inst> = HashSet::new();
292 let mut moved: HashSet<Value> = HashSet::new();
293 let mut room = [0; Class::COUNT];
304 for class in Class::ALL {
305 room[class.index()] = self.machine.allocatable(class).unwrap_or(0);
306 }
307
308 let mut reaching = !spins;
313
314 for block in self.cfg.reverse_postorder() {
315 if !inside.contains(&block) {
316 continue;
317 }
318 let entered = self.post.post_dominates(block, header);
319 for inst in func.insts(block) {
320 let runs = reaching && entered;
324 if !goes_on(func, inst, &mut ranges, block) {
325 reaching = false;
326 }
327 if func.is_terminator(inst) {
328 continue;
329 }
330 let Some(result) = func[inst].results().next() else {
331 continue;
332 };
333 let Some(class) = class_of(func[result].ty) else {
334 continue;
335 };
336 if !self.unchanging(func, id, inst, &moved) {
337 continue;
338 }
339 let settled = asks_the_plane(func[inst].opcode) && !ends;
348 if writes
349 && !settled
350 && func[inst].opcode.touches_memory()
351 && func.mem_in(inst).is_none()
352 {
353 stats.missed(MEMORY);
354 continue;
355 }
356 let cost = cost(func, inst);
357 match movement(speculate::why_not(func, inst, &mut ranges, preheader)) {
358 Move::Anywhere => (),
359 Move::IfItWasGoingToRun if runs => (),
360 Move::IfItWasGoingToRun => {
361 stats.missed(SPECULATIVE);
362 continue;
363 }
364 Move::Nowhere => {
365 stats.missed(EFFECTS);
366 continue;
367 }
368 }
369 let bank = class.index();
370 if cost > 0 {
373 let tight = pressure.is_tight(self.loops, id, class, room[bank]);
374 if tight && cost < heuristics::LICM_EXPENSIVE {
375 passengers.insert(inst);
376 }
377 if !fuel.take() {
378 stats.missed(NO_FUEL);
379 return trim(func, plan, &passengers, stats);
380 }
381 room[bank] = room[bank].saturating_sub(1);
382 }
383 moved.extend(func[inst].results());
384 plan.push(inst);
385 }
386 }
387 trim(func, plan, &passengers, stats)
388 }
389
390 fn unchanging(&self, func: &Func, id: LoopId, inst: Inst, moved: &HashSet<Value>) -> bool {
395 func[func[inst].args]
396 .iter()
397 .all(|arg| self.loops.is_invariant(func, id, *arg) || moved.contains(arg))
398 }
399}
400
401fn trim(func: &Func, plan: Vec<Inst>, passengers: &HashSet<Inst>, stats: &mut Stats) -> Vec<Inst> {
420 let mut wanted: HashSet<Value> = HashSet::new();
421 let mut keep = Vec::with_capacity(plan.len());
422 for inst in plan.into_iter().rev() {
423 if !func[inst].results().any(|value| wanted.contains(&value)) {
424 if cost(func, inst) == 0 {
425 continue;
426 }
427 if passengers.contains(&inst) {
428 stats.missed(PRESSURE);
429 continue;
430 }
431 }
432 wanted.extend(func[func[inst].args].iter().copied());
433 keep.push(inst);
434 }
435 keep.reverse();
436 keep
437}
438
439fn movement(why: Option<&'static str>) -> Move {
444 match why {
445 None => Move::Anywhere,
446 Some(speculate::BY_ZERO | speculate::OVERFLOW | speculate::ADDRESS) => {
448 Move::IfItWasGoingToRun
449 }
450 Some(_) => Move::Nowhere,
451 }
452}
453
454fn goes_on(func: &Func, inst: Inst, ranges: &mut Ranges<'_>, at: Block) -> bool {
470 match func[inst].opcode {
471 Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => false,
472 Opcode::UnreachableHint => false,
474 Opcode::SDiv | Opcode::SRem | Opcode::UDiv | Opcode::URem | Opcode::Load => {
475 movement(speculate::why_not(func, inst, ranges, at)) != Move::IfItWasGoingToRun
476 }
477 _ => true,
478 }
479}
480
481const fn asks_the_plane(opcode: Opcode) -> bool {
495 matches!(opcode, Opcode::CapExtent | Opcode::CapExtentBack)
496}
497
498fn ends_a_lifetime(func: &Func, inst: Inst) -> bool {
506 match func[inst].opcode {
507 Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => {
508 !func[inst].flags.contains(Flags::NOFREE)
509 }
510 Opcode::InlineAsm | Opcode::MetaEnd | Opcode::MetaTransfer => true,
511 _ => false,
512 }
513}
514
515fn cost(func: &Func, inst: Inst) -> u32 {
525 match func[inst].opcode {
526 Opcode::IConst | Opcode::FConst | Opcode::GlobalAddr | Opcode::BlockAddr => 0,
532 Opcode::CapOf => 0,
536 Opcode::Load
537 | Opcode::Select
538 | Opcode::Call
539 | Opcode::CallIndirect
540 | Opcode::Mul
541 | Opcode::SDiv
542 | Opcode::UDiv
543 | Opcode::SRem
544 | Opcode::URem
545 | Opcode::FMul
546 | Opcode::FDiv
547 | Opcode::FRem
548 | Opcode::Shl
549 | Opcode::LShr
550 | Opcode::AShr
551 | Opcode::ICmp
552 | Opcode::FCmp => heuristics::LICM_EXPENSIVE,
553 Opcode::CapExtent | Opcode::CapExtentBack => heuristics::LICM_EXPENSIVE,
558 _ => 1,
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use rucc_base::{Interner, Symbol};
565 use rucc_ir::{
566 Block, Builder, Def, Extra, Flags, Func, Global, Inst, InstData, IntPred, MemInfo,
567 MemOrder, Module, Opcode, Restrict, Signature, Type, Value, verify_func,
568 };
569 use rucc_target::{TargetInfo, Triple};
570
571 use super::{
572 EFFECTS, HOISTED, LICM, MEMORY, NO_FUEL, NO_PREHEADER, PRESSURE, SPECULATIVE, SPINS,
573 };
574 use crate::canon::Canon;
575 use crate::header_copy::SPEED;
576 use crate::stats::Kind;
577 use crate::{Fuel, Pass, Stats};
578
579 fn hoist(func: &mut Func, fuel: &mut Fuel) -> Stats {
581 LICM.run(func, &mut crate::machine::fixtures::analyses(), fuel)
582 }
583
584 fn sound(func: &Func, names: &mut Interner) {
589 checked(func, names, &[]);
590 }
591
592 fn checked(func: &Func, names: &mut Interner, globals: &[Symbol]) {
594 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
595 let mut module = Module::new(names.intern("t.c"), &target);
596 for name in globals {
597 module.add_global(Global::new(*name, 16, 8));
598 }
599 if let Err(errors) = verify_func(&module, func, names) {
600 panic!("{errors:#?}");
601 }
602 }
603
604 fn made(func: &Func, value: Value) -> Inst {
606 match func[value].def {
607 Def::Result { inst, .. } => inst,
608 other => panic!("{other:?} is not something an instruction worked out"),
609 }
610 }
611
612 fn lives_in(func: &Func, value: Value) -> Block {
614 func.block_of(made(func, value)).expect("it is in a block")
615 }
616
617 fn position(func: &Func, value: Value) -> usize {
619 let inst = made(func, value);
620 let block = func.block_of(inst).expect("it is in a block");
621 func.insts(block).position(|other| other == inst).expect("it is in that block")
622 }
623
624 fn tucked(func: &mut Func, block: Block) {
631 let term = func
632 .insts(block)
633 .find(|inst| func.is_terminator(*inst))
634 .expect("the block ends in something");
635 let stragglers: Vec<Inst> =
636 func.insts(block).skip_while(|inst| *inst != term).skip(1).collect();
637 for inst in stragglers {
638 func.remove_inst(inst);
639 func.insert_before(inst, term);
640 }
641 }
642
643 fn record(size: u64) -> MemInfo {
645 MemInfo {
646 size,
647 align: 8,
648 order: MemOrder::NotAtomic,
649 tbaa: None,
650 owns: 0,
651 restrict: Restrict::NONE,
652 }
653 }
654
655 struct Counted {
668 names: Interner,
669 func: Func,
670 entry: Block,
671 head: Block,
672 body: Block,
673 limit: Value,
674 pointer: Value,
675 }
676
677 fn counted(spare: usize) -> Counted {
678 let mut names = Interner::new();
679 let mut types = vec![Type::int(32); spare + 1];
680 types.push(Type::PTR);
681 let signature = Signature::new().with_params(&types).with_returns(&[Type::int(32)]);
682 let mut func = Func::new(names.intern("f"), signature);
683 let entry = func.create_block();
684 let head = func.create_block();
685 let body = func.create_block();
686 let done = func.create_block();
687 let handed: Vec<Value> =
688 types.iter().map(|ty| func.append_param(entry, *ty)).collect::<Vec<_>>();
689 let limit = handed[0];
690 let pointer = *handed.last().expect("the pointer is the last of them");
691 let i = func.append_param(head, Type::int(32));
692 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
693 Builder::new(&mut func, entry).jump(head, &[zero]);
694 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
695 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
696 let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
697 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
698 Builder::new(&mut func, body).jump(head, &[next]);
699 let mut build = Builder::new(&mut func, done);
700 let mut total = i;
701 for value in &handed[1..=spare] {
702 total = build.binary(Opcode::Add, total, *value, Flags::NONE);
703 }
704 build.ret(&[total]);
705 Counted { names, func, entry, head, body, limit, pointer }
706 }
707
708 #[test]
709 fn an_invariant_computation_moves_in_front_of_the_loop() {
710 let mut it = counted(0);
711 let product = Builder::new(&mut it.func, it.body).binary(
712 Opcode::Mul,
713 it.limit,
714 it.limit,
715 Flags::NONE,
716 );
717 tucked(&mut it.func, it.body);
718
719 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
720 assert_eq!(stats.count(Kind::Optimized, HOISTED), 1);
721 assert_eq!(lives_in(&it.func, product), it.entry, "it is in front of the loop now");
722 sound(&it.func, &mut it.names);
723 }
724
725 #[test]
726 fn a_computation_the_loop_changes_stays_where_it_is() {
727 let mut it = counted(0);
728 let i = it.func[it.head].params[0];
729 let square = Builder::new(&mut it.func, it.body).binary(Opcode::Mul, i, i, Flags::NONE);
730 tucked(&mut it.func, it.body);
731
732 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
733 assert_eq!(stats.count(Kind::Optimized, HOISTED), 0);
734 assert_eq!(lives_in(&it.func, square), it.body);
735 sound(&it.func, &mut it.names);
736 }
737
738 #[test]
739 fn a_loop_with_nothing_invariant_in_it_is_left_alone() {
740 let mut it = counted(0);
744 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
745 assert_eq!(stats.count(Kind::Optimized, HOISTED), 0);
746 sound(&it.func, &mut it.names);
747 }
748
749 #[test]
750 fn a_load_the_loop_might_not_reach_stays_where_it_is() {
751 let mut it = counted(0);
752 let read = Builder::new(&mut it.func, it.body).load(
753 Type::int(32),
754 it.pointer,
755 record(4),
756 Flags::NONE,
757 );
758 tucked(&mut it.func, it.body);
759
760 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
761 assert_eq!(stats.count(Kind::Optimized, HOISTED), 0);
762 assert_eq!(stats.count(Kind::Missed, SPECULATIVE), 1);
763 assert_eq!(lives_in(&it.func, read), it.body, "the loop may run zero times");
764 sound(&it.func, &mut it.names);
765 }
766
767 #[test]
768 fn the_same_load_moves_once_the_loop_tests_at_the_bottom() {
769 let mut it = counted(0);
776 let read = Builder::new(&mut it.func, it.body).load(
777 Type::int(32),
778 it.pointer,
779 record(4),
780 Flags::NONE,
781 );
782 tucked(&mut it.func, it.body);
783 let mut an = crate::machine::fixtures::analyses();
784 Canon.run(&mut it.func, &mut an, &mut Fuel::unlimited());
785 SPEED.run(&mut it.func, &mut an, &mut Fuel::unlimited());
786 Canon.run(&mut it.func, &mut an, &mut Fuel::unlimited());
787
788 let stats = LICM.run(&mut it.func, &mut an, &mut Fuel::unlimited());
789 assert_eq!(stats.count(Kind::Optimized, HOISTED), 1);
790 assert_ne!(lives_in(&it.func, read), it.body, "it left the body");
791 sound(&it.func, &mut it.names);
792 }
793
794 #[test]
795 fn something_that_could_trap_moves_when_it_runs_on_every_entry() {
796 let mut it = counted(0);
799 let share = Builder::new(&mut it.func, it.head).binary(
800 Opcode::SDiv,
801 it.limit,
802 it.limit,
803 Flags::NONE,
804 );
805 tucked(&mut it.func, it.head);
806
807 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
808 assert_eq!(stats.count(Kind::Optimized, HOISTED), 1);
809 assert_eq!(lives_in(&it.func, share), it.entry);
810 sound(&it.func, &mut it.names);
811 }
812
813 #[test]
814 fn something_that_could_trap_stays_behind_a_call_that_might_not_come_back() {
815 let mut it = counted(0);
820 let callee = it.names.intern("g");
821 let mut build = Builder::new(&mut it.func, it.head);
822 let signature = build.func().add_signature(Signature::new());
823 build.call(callee, signature, &[]);
824 let share = Builder::new(&mut it.func, it.head).binary(
825 Opcode::SDiv,
826 it.limit,
827 it.limit,
828 Flags::NONE,
829 );
830 tucked(&mut it.func, it.head);
831
832 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
833 assert_eq!(stats.count(Kind::Missed, SPECULATIVE), 1);
834 assert_eq!(lives_in(&it.func, share), it.head, "the call in front is what keeps it there");
835 sound(&it.func, &mut it.names);
836 }
837
838 #[test]
839 fn the_same_division_moves_when_the_call_is_behind_it() {
840 let mut it = counted(0);
843 let callee = it.names.intern("g");
844 let share = Builder::new(&mut it.func, it.head).binary(
845 Opcode::SDiv,
846 it.limit,
847 it.limit,
848 Flags::NONE,
849 );
850 let mut build = Builder::new(&mut it.func, it.head);
851 let signature = build.func().add_signature(Signature::new());
852 build.call(callee, signature, &[]);
853 tucked(&mut it.func, it.head);
854
855 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
856 assert_eq!(stats.count(Kind::Optimized, HOISTED), 1);
857 assert_eq!(lives_in(&it.func, share), it.entry);
858 sound(&it.func, &mut it.names);
859 }
860
861 #[test]
862 fn a_call_in_one_block_keeps_something_in_a_later_one_where_it_is() {
863 let mut names = Interner::new();
868 let signature =
869 Signature::new().with_params(&[Type::int(32)]).with_returns(&[Type::int(32)]);
870 let mut func = Func::new(names.intern("f"), signature);
871 let callee = names.intern("g");
872 let entry = func.create_block();
873 let head = func.create_block();
874 let rest = func.create_block();
875 let done = func.create_block();
876 let limit = func.append_param(entry, Type::int(32));
877 let i = func.append_param(head, Type::int(32));
878 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
879 Builder::new(&mut func, entry).jump(head, &[zero]);
880 let mut build = Builder::new(&mut func, head);
881 let taken = build.func().add_signature(Signature::new());
882 build.call(callee, taken, &[]);
883 Builder::new(&mut func, head).jump(rest, &[]);
884 let share = Builder::new(&mut func, rest).binary(Opcode::SDiv, limit, limit, Flags::NONE);
885 let one = Builder::new(&mut func, rest).iconst(Type::int(32), 1);
886 let next = Builder::new(&mut func, rest).binary(Opcode::Add, i, one, Flags::NONE);
887 let test = Builder::new(&mut func, rest).icmp(IntPred::Slt, next, limit);
888 Builder::new(&mut func, rest).br_if(test, head, &[next], done, &[]);
889 Builder::new(&mut func, done).ret(&[i]);
890
891 let stats = hoist(&mut func, &mut Fuel::unlimited());
892 assert_eq!(stats.count(Kind::Missed, SPECULATIVE), 1);
893 assert_eq!(lives_in(&func, share), rest, "the call is in front of it in the same turn");
894 sound(&func, &mut names);
895 }
896
897 #[test]
898 fn a_division_a_test_inside_the_loop_made_safe_stays_inside_that_test() {
899 let mut names = Interner::new();
904 let signature =
905 Signature::new().with_params(&[Type::int(32)]).with_returns(&[Type::int(32)]);
906 let mut func = Func::new(names.intern("f"), signature);
907 let entry = func.create_block();
908 let head = func.create_block();
909 let body = func.create_block();
910 let safe = func.create_block();
911 let latch = func.create_block();
912 let done = func.create_block();
913 let limit = func.append_param(entry, Type::int(32));
914 let i = func.append_param(head, Type::int(32));
915 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
916 Builder::new(&mut func, entry).jump(head, &[zero]);
917 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
918 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
919 let guard = Builder::new(&mut func, body).icmp(IntPred::Ne, limit, zero);
920 Builder::new(&mut func, body).br_if(guard, safe, &[], latch, &[]);
921 let share = Builder::new(&mut func, safe).binary(Opcode::SDiv, limit, limit, Flags::NONE);
922 Builder::new(&mut func, safe).jump(latch, &[]);
923 let one = Builder::new(&mut func, latch).iconst(Type::int(32), 1);
924 let next = Builder::new(&mut func, latch).binary(Opcode::Add, i, one, Flags::NONE);
925 Builder::new(&mut func, latch).jump(head, &[next]);
926 Builder::new(&mut func, done).ret(&[i]);
927
928 let stats = hoist(&mut func, &mut Fuel::unlimited());
929 assert_eq!(stats.count(Kind::Missed, SPECULATIVE), 1);
930 assert_eq!(lives_in(&func, share), safe, "the guard is what made it safe");
931 assert_eq!(lives_in(&func, guard), entry);
934 assert_eq!(stats.count(Kind::Optimized, HOISTED), 1);
935 sound(&func, &mut names);
936 }
937
938 #[test]
939 fn the_same_division_in_the_body_stays() {
940 let mut it = counted(0);
941 let share = Builder::new(&mut it.func, it.body).binary(
942 Opcode::SDiv,
943 it.limit,
944 it.limit,
945 Flags::NONE,
946 );
947 tucked(&mut it.func, it.body);
948
949 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
950 assert_eq!(stats.count(Kind::Missed, SPECULATIVE), 1);
951 assert_eq!(lives_in(&it.func, share), it.body, "the divisor could be zero");
952 sound(&it.func, &mut it.names);
953 }
954
955 #[test]
956 fn a_volatile_load_stays_even_where_it_runs_on_every_entry() {
957 let mut it = counted(0);
958 let read = Builder::new(&mut it.func, it.head).load(
959 Type::int(32),
960 it.pointer,
961 record(4),
962 Flags::VOLATILE,
963 );
964 tucked(&mut it.func, it.head);
965
966 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
967 assert_eq!(stats.count(Kind::Missed, EFFECTS), 1);
968 assert_eq!(lives_in(&it.func, read), it.head, "one access per iteration is the point");
969 sound(&it.func, &mut it.names);
970 }
971
972 #[test]
973 fn a_chain_comes_out_in_the_order_it_was_written_in() {
974 let mut it = counted(0);
977 let mut build = Builder::new(&mut it.func, it.body);
978 let product = build.binary(Opcode::Mul, it.limit, it.limit, Flags::NONE);
979 let sum = build.binary(Opcode::Mul, product, it.limit, Flags::NONE);
980 tucked(&mut it.func, it.body);
981
982 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
983 assert_eq!(stats.count(Kind::Optimized, HOISTED), 2);
984 assert_eq!(lives_in(&it.func, product), it.entry);
985 assert_eq!(lives_in(&it.func, sum), it.entry);
986 assert!(position(&it.func, product) < position(&it.func, sum));
987 sound(&it.func, &mut it.names);
988 }
989
990 #[test]
991 fn the_pass_stops_where_the_fuel_runs_out() {
992 let mut it = counted(0);
993 let mut build = Builder::new(&mut it.func, it.body);
994 let product = build.binary(Opcode::Mul, it.limit, it.limit, Flags::NONE);
995 build.binary(Opcode::Mul, product, it.limit, Flags::NONE);
996 tucked(&mut it.func, it.body);
997
998 let stats = hoist(&mut it.func, &mut Fuel::of(1));
999 assert_eq!(stats.count(Kind::Optimized, HOISTED), 1);
1000 assert_eq!(stats.count(Kind::Missed, NO_FUEL), 1);
1001 sound(&it.func, &mut it.names);
1002 }
1003
1004 #[test]
1005 fn a_cheap_computation_stays_where_the_loop_is_already_full() {
1006 let mut it = counted(14);
1010 let mut build = Builder::new(&mut it.func, it.body);
1011 let sum = build.binary(Opcode::Add, it.limit, it.limit, Flags::NONE);
1012 let product = build.binary(Opcode::Mul, it.limit, it.limit, Flags::NONE);
1013 tucked(&mut it.func, it.body);
1014
1015 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1016 assert_eq!(stats.count(Kind::Missed, PRESSURE), 1);
1017 assert_eq!(lives_in(&it.func, sum), it.body);
1018 assert_eq!(lives_in(&it.func, product), it.entry);
1019 sound(&it.func, &mut it.names);
1020 }
1021
1022 #[test]
1023 fn a_cheap_link_moves_when_it_is_carrying_an_expensive_one_out() {
1024 let mut it = counted(14);
1029 let mut build = Builder::new(&mut it.func, it.body);
1030 let sum = build.binary(Opcode::Add, it.limit, it.limit, Flags::NONE);
1031 let product = build.binary(Opcode::Mul, sum, it.limit, Flags::NONE);
1032 tucked(&mut it.func, it.body);
1033
1034 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1035 assert_eq!(stats.count(Kind::Missed, PRESSURE), 0);
1036 assert_eq!(lives_in(&it.func, sum), it.entry, "it is carrying the multiply");
1037 assert_eq!(lives_in(&it.func, product), it.entry);
1038 sound(&it.func, &mut it.names);
1039 }
1040
1041 #[test]
1042 fn a_chain_of_cheap_links_that_carries_nothing_stays_where_it_is() {
1043 let mut it = counted(14);
1047 let mut build = Builder::new(&mut it.func, it.body);
1048 let first = build.binary(Opcode::Add, it.limit, it.limit, Flags::NONE);
1049 let second = build.binary(Opcode::Add, first, it.limit, Flags::NONE);
1050 tucked(&mut it.func, it.body);
1051
1052 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1053 assert_eq!(stats.count(Kind::Missed, PRESSURE), 2);
1054 assert_eq!(stats.count(Kind::Optimized, HOISTED), 0);
1055 assert_eq!(lives_in(&it.func, first), it.body);
1056 assert_eq!(lives_in(&it.func, second), it.body);
1057 sound(&it.func, &mut it.names);
1058 }
1059
1060 #[test]
1061 fn the_same_add_moves_when_the_loop_has_room() {
1062 let mut it = counted(0);
1063 let sum = Builder::new(&mut it.func, it.body).binary(
1064 Opcode::Add,
1065 it.limit,
1066 it.limit,
1067 Flags::NONE,
1068 );
1069 tucked(&mut it.func, it.body);
1070
1071 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1072 assert_eq!(stats.count(Kind::Missed, PRESSURE), 0);
1073 assert_eq!(lives_in(&it.func, sum), it.entry);
1074 sound(&it.func, &mut it.names);
1075 }
1076
1077 #[test]
1078 fn a_loop_with_two_ways_in_is_left_alone() {
1079 let mut names = Interner::new();
1081 let signature = Signature::new().with_params(&[Type::I1, Type::int(32)]);
1082 let mut func = Func::new(names.intern("f"), signature);
1083 let entry = func.create_block();
1084 let low = func.create_block();
1085 let high = func.create_block();
1086 let head = func.create_block();
1087 let body = func.create_block();
1088 let done = func.create_block();
1089 let either = func.append_param(entry, Type::I1);
1090 let n = func.append_param(entry, Type::int(32));
1091 let i = func.append_param(head, Type::int(32));
1092 Builder::new(&mut func, entry).br_if(either, low, &[], high, &[]);
1093 let zero = Builder::new(&mut func, low).iconst(Type::int(32), 0);
1094 Builder::new(&mut func, low).jump(head, &[zero]);
1095 let one = Builder::new(&mut func, high).iconst(Type::int(32), 1);
1096 Builder::new(&mut func, high).jump(head, &[one]);
1097 let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, n);
1098 Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
1099 let product = Builder::new(&mut func, body).binary(Opcode::Mul, n, n, Flags::NONE);
1100 let next = Builder::new(&mut func, body).binary(Opcode::Add, i, product, Flags::NONE);
1101 Builder::new(&mut func, body).jump(head, &[next]);
1102 Builder::new(&mut func, done).ret(&[]);
1103
1104 let stats = hoist(&mut func, &mut Fuel::unlimited());
1105 assert_eq!(stats.count(Kind::Missed, NO_PREHEADER), 1);
1106 assert_eq!(lives_in(&func, product), body);
1107 sound(&func, &mut names);
1108 }
1109
1110 #[test]
1111 fn a_loop_with_no_way_out_gets_the_pure_hoist_and_not_the_other_one() {
1112 let mut names = Interner::new();
1115 let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]);
1116 let mut func = Func::new(names.intern("f"), signature);
1117 let entry = func.create_block();
1118 let head = func.create_block();
1119 let n = func.append_param(entry, Type::int(32));
1120 let pointer = func.append_param(entry, Type::PTR);
1121 Builder::new(&mut func, entry).jump(head, &[]);
1122 let mut build = Builder::new(&mut func, head);
1123 let product = build.binary(Opcode::Mul, n, n, Flags::NONE);
1124 let read = build.load(Type::int(32), pointer, record(4), Flags::NONE);
1125 build.jump(head, &[]);
1126
1127 let stats = hoist(&mut func, &mut Fuel::unlimited());
1128 assert_eq!(stats.count(Kind::Missed, SPINS), 1);
1129 assert_eq!(stats.count(Kind::Missed, SPECULATIVE), 1);
1130 assert_eq!(lives_in(&func, product), entry, "arithmetic is safe anywhere");
1131 assert_eq!(lives_in(&func, read), head, "the address is still one nobody has vouched for");
1132 sound(&func, &mut names);
1133 }
1134
1135 #[test]
1136 fn an_invariant_comes_all_the_way_out_of_a_nest_in_one_run() {
1137 let mut names = Interner::new();
1141 let signature = Signature::new().with_params(&[Type::int(32)]);
1142 let mut func = Func::new(names.intern("f"), signature);
1143 let entry = func.create_block();
1144 let outer = func.create_block();
1145 let ready = func.create_block();
1146 let inner = func.create_block();
1147 let deep = func.create_block();
1148 let latch = func.create_block();
1149 let done = func.create_block();
1150 let n = func.append_param(entry, Type::int(32));
1151 let i = func.append_param(outer, Type::int(32));
1152 let j = func.append_param(inner, Type::int(32));
1153 let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1154 Builder::new(&mut func, entry).jump(outer, &[zero]);
1155 let outer_test = Builder::new(&mut func, outer).icmp(IntPred::Slt, i, n);
1156 Builder::new(&mut func, outer).br_if(outer_test, ready, &[], done, &[]);
1157 let start = Builder::new(&mut func, ready).iconst(Type::int(32), 0);
1158 Builder::new(&mut func, ready).jump(inner, &[start]);
1159 let inner_test = Builder::new(&mut func, inner).icmp(IntPred::Slt, j, n);
1160 Builder::new(&mut func, inner).br_if(inner_test, deep, &[], latch, &[]);
1161 let mut build = Builder::new(&mut func, deep);
1162 let product = build.binary(Opcode::Mul, n, n, Flags::NONE);
1163 let one = build.iconst(Type::int(32), 1);
1164 let next_j = build.binary(Opcode::Add, j, one, Flags::NONE);
1165 build.jump(inner, &[next_j]);
1166 let mut build = Builder::new(&mut func, latch);
1167 let step = build.iconst(Type::int(32), 1);
1168 let next_i = build.binary(Opcode::Add, i, step, Flags::NONE);
1169 build.jump(outer, &[next_i]);
1170 Builder::new(&mut func, done).ret(&[]);
1171
1172 let stats = hoist(&mut func, &mut Fuel::unlimited());
1173 assert_eq!(stats.count(Kind::Optimized, HOISTED), 2, "one level and then the other");
1174 assert_eq!(lives_in(&func, product), entry);
1175 sound(&func, &mut names);
1176 }
1177
1178 #[test]
1179 fn a_function_with_no_loop_in_it_is_untouched() {
1180 let mut names = Interner::new();
1181 let mut func = Func::new(names.intern("f"), Signature::new());
1182 let entry = func.create_block();
1183 Builder::new(&mut func, entry).ret(&[]);
1184
1185 let stats = hoist(&mut func, &mut Fuel::unlimited());
1186 assert!(!stats.changed());
1187 sound(&func, &mut names);
1188 }
1189
1190 #[test]
1191 fn the_address_of_a_global_moves_only_when_something_that_reads_it_moves() {
1192 let mut it = counted(0);
1197 let grid = it.names.intern("grid");
1198 let mut build = Builder::new(&mut it.func, it.head);
1199 let at = build.value(
1200 InstData { extra: Extra::Symbol(grid), ..InstData::new(Opcode::GlobalAddr) },
1201 Type::PTR,
1202 );
1203 let read = build.load(Type::int(32), at, record(4), Flags::NONE);
1204 tucked(&mut it.func, it.head);
1205
1206 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1207 assert_eq!(stats.count(Kind::Optimized, HOISTED), 2, "the load and its address");
1208 assert_eq!(lives_in(&it.func, at), it.entry);
1209 assert_eq!(lives_in(&it.func, read), it.entry);
1210 assert!(position(&it.func, at) < position(&it.func, read));
1211 checked(&it.func, &mut it.names, &[grid]);
1212 }
1213
1214 #[test]
1215 fn the_address_of_a_global_on_its_own_stays_where_it_is() {
1216 let mut it = counted(0);
1219 let grid = it.names.intern("grid");
1220 let at = Builder::new(&mut it.func, it.body).value(
1221 InstData { extra: Extra::Symbol(grid), ..InstData::new(Opcode::GlobalAddr) },
1222 Type::PTR,
1223 );
1224 tucked(&mut it.func, it.body);
1225
1226 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1227 assert_eq!(stats.count(Kind::Optimized, HOISTED), 0);
1228 assert_eq!(lives_in(&it.func, at), it.body);
1229 checked(&it.func, &mut it.names, &[grid]);
1230 }
1231
1232 #[test]
1234 fn the_same_load_stays_once_the_loop_writes_anything_at_all() {
1235 let mut it = counted(0);
1241 let mem = it.func.add_mem(record(4));
1242 let slot = Builder::new(&mut it.func, it.entry)
1243 .value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR);
1244 tucked(&mut it.func, it.entry);
1245 let mut build = Builder::new(&mut it.func, it.body);
1246 let read = build.load(Type::int(32), slot, record(4), Flags::NONE);
1247 build.store(read, it.pointer, record(4), Flags::NONE);
1248 tucked(&mut it.func, it.body);
1249
1250 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1251 assert_eq!(stats.count(Kind::Missed, MEMORY), 1);
1252 assert_eq!(lives_in(&it.func, read), it.body);
1253 sound(&it.func, &mut it.names);
1254 }
1255
1256 fn extent(func: &mut Func, block: Block, pointer: Value) -> Value {
1258 let mut build = Builder::new(func, block);
1259 let want = build.iconst(Type::int(64), i128::from(i64::MAX));
1260 let args = build.func().push_values(&[pointer]);
1261 let of = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1262 let args = build.func().push_values(&[of, pointer, want]);
1263 build.value(InstData { args, ..InstData::new(Opcode::CapExtent) }, Type::int(64))
1264 }
1265
1266 #[test]
1267 fn asking_how_big_an_object_is_moves_out_of_a_loop_that_writes_to_it() {
1268 let mut it = counted(0);
1273 let asked = extent(&mut it.func, it.body, it.pointer);
1274 let mut build = Builder::new(&mut it.func, it.body);
1275 let byte = build.iconst(Type::int(32), 0);
1276 build.store(byte, it.pointer, record(4), Flags::NONE);
1277 tucked(&mut it.func, it.body);
1278
1279 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1280 assert_eq!(stats.count(Kind::Missed, MEMORY), 0);
1281 assert_eq!(lives_in(&it.func, asked), it.entry, "it is in front of the loop now");
1282 sound(&it.func, &mut it.names);
1283 }
1284
1285 #[test]
1286 fn asking_how_big_an_object_is_stays_in_a_loop_that_calls_something_that_could_free() {
1287 let mut it = counted(0);
1291 let asked = extent(&mut it.func, it.body, it.pointer);
1292 let signature = it.func.add_signature(Signature::new().with_params(&[Type::PTR]));
1293 let callee = it.names.intern("might_free");
1294 Builder::new(&mut it.func, it.body).call(callee, signature, &[it.pointer]);
1295 tucked(&mut it.func, it.body);
1296
1297 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1298 assert_eq!(stats.count(Kind::Missed, MEMORY), 1);
1299 assert_eq!(lives_in(&it.func, asked), it.body);
1300 sound(&it.func, &mut it.names);
1301 }
1302
1303 #[test]
1304 fn asking_how_big_an_object_is_moves_past_a_call_the_summary_says_cannot_free() {
1305 let mut it = counted(0);
1309 let asked = extent(&mut it.func, it.body, it.pointer);
1310 let signature = it.func.add_signature(Signature::new().with_params(&[Type::PTR]));
1311 let callee = it.names.intern("cannot_free");
1312 let call = Builder::new(&mut it.func, it.body).call(callee, signature, &[it.pointer]);
1313 it.func[call].flags |= Flags::NOFREE;
1314 tucked(&mut it.func, it.body);
1315
1316 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1317 assert_eq!(stats.count(Kind::Missed, MEMORY), 0);
1318 assert_eq!(lives_in(&it.func, asked), it.entry, "it is in front of the loop now");
1319 sound(&it.func, &mut it.names);
1320 }
1321
1322 #[test]
1323 fn a_load_of_a_local_the_loop_does_not_write_moves_out_of_the_body() {
1324 let mut it = counted(0);
1325 let mem = it.func.add_mem(record(4));
1326 let slot = Builder::new(&mut it.func, it.entry)
1327 .value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR);
1328 tucked(&mut it.func, it.entry);
1329 let read =
1330 Builder::new(&mut it.func, it.body).load(Type::int(32), slot, record(4), Flags::NONE);
1331 tucked(&mut it.func, it.body);
1332
1333 let stats = hoist(&mut it.func, &mut Fuel::unlimited());
1334 assert_eq!(stats.count(Kind::Optimized, HOISTED), 1);
1335 assert_eq!(lives_in(&it.func, read), it.entry, "four bytes of four are always there");
1336 sound(&it.func, &mut it.names);
1337 }
1338}