1use std::collections::{HashMap, HashSet};
86
87use rucc_ir::{Block, BlockCall, Def, Flags, Func, Inst, InstData, MemOrder, Opcode, Type, Value};
88
89use crate::alias::{Access, Alias, Answer, Options};
90use crate::cfg::Cfg;
91use crate::dom::Dominators;
92use crate::outside::Outside;
93
94pub const MAX_ALIAS_QUERIES_PER_ACCESS: u32 = 1000;
99
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub enum Clobber {
108 Exact(Inst),
113 Partial(Inst),
119 Maybe(Inst),
121 NoClobber,
123 Unknown,
125}
126
127impl Clobber {
128 #[must_use]
130 pub const fn inst(self) -> Option<Inst> {
131 match self {
132 Self::Exact(inst) | Self::Partial(inst) | Self::Maybe(inst) => Some(inst),
133 Self::NoClobber | Self::Unknown => None,
134 }
135 }
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub enum Step {
145 Stop,
147 Retry(Access),
149}
150
151#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
158pub struct Counts {
159 walks: u64,
160 steps: u64,
161 exhausted: u64,
162}
163
164impl Counts {
165 #[must_use]
167 pub const fn walks(&self) -> u64 {
168 self.walks
169 }
170
171 #[must_use]
173 pub const fn steps(&self) -> u64 {
174 self.steps
175 }
176
177 #[must_use]
182 pub const fn exhausted(&self) -> u64 {
183 self.exhausted
184 }
185}
186
187pub fn build(func: &mut Func) -> bool {
200 let Some(entry) = func.entry() else {
201 return false;
202 };
203 let cfg = Cfg::new(func);
204 let doms = Dominators::new(&cfg);
205
206 let mut defs = vec![entry];
208 let mut any = false;
209 for block in func.blocks() {
210 if !cfg.reaches(block) {
215 return false;
216 }
217 let mut writes = false;
218 for inst in func.insts(block) {
219 if func.carries_mem(inst) {
220 return false;
221 }
222 let opcode = func[inst].opcode;
223 any |= opcode.touches_memory();
224 writes |= opcode.writes_memory();
225 }
226 if writes && block != entry {
227 defs.push(block);
228 }
229 }
230 let Some(first) = func.insts(entry).next() else {
233 return false;
234 };
235 if !any {
236 return false;
237 }
238
239 let joins = iterated_frontier(&cfg, &doms, &defs);
240 let mut params = HashMap::new();
241 for block in func.blocks().collect::<Vec<_>>() {
242 if joins.contains(&block) {
243 params.insert(block, func.append_param(block, Type::MEM));
244 }
245 }
246
247 let start = start_of_chain(func, first);
248 let ends = thread(func, &doms, ¶ms, entry, start);
249 pass_it_on(func, ¶ms, &ends);
250 true
251}
252
253pub fn strip(func: &mut Func) -> bool {
274 let mut forward: Vec<(Value, Value)> = Vec::new();
275 let mut gone: Vec<Inst> = Vec::new();
276 let mut entry = None;
277 for block in func.blocks().collect::<Vec<Block>>() {
278 for inst in func.insts(block).collect::<Vec<Inst>>() {
279 if func[inst].opcode == Opcode::MemEntry {
280 entry = Some(inst);
281 continue;
282 }
283 if !func.carries_mem(inst) {
284 continue;
285 }
286 let bare = func.without_mem(inst);
287 func.insert_before(bare, inst);
288 for (old, new) in func[inst].results().zip(func[bare].results()) {
292 forward.push((old, new));
293 }
294 gone.push(inst);
295 }
296 }
297 if entry.is_none() && gone.is_empty() {
298 return false;
299 }
300 for inst in gone {
301 func.remove_inst(inst);
302 }
303 let forward: HashMap<Value, Value> = forward.into_iter().collect();
304 if !forward.is_empty() {
305 substitute(func, &forward);
306 }
307 drop_params(func);
308 if let Some(inst) = entry {
309 func.remove_inst(inst);
310 }
311 true
312}
313
314fn drop_params(func: &mut Func) {
322 let mut at: HashMap<Block, Vec<usize>> = HashMap::new();
323 let mut going: HashSet<Value> = HashSet::new();
324 for block in func.blocks().collect::<Vec<Block>>() {
325 let mut keep = Vec::new();
326 for (index, ¶m) in func[block].params.iter().enumerate() {
327 if func[param].ty.is_mem() {
328 going.insert(param);
329 } else {
330 keep.push(index);
331 }
332 }
333 if keep.len() != func[block].params.len() {
334 at.insert(block, keep);
335 }
336 }
337 if at.is_empty() {
338 return;
339 }
340 for block in func.blocks().collect::<Vec<Block>>() {
341 let Some(terminator) = func.terminator(block) else {
342 continue;
343 };
344 for target in func.target_list(terminator).iter() {
345 let call = func[target];
346 let Some(keep) = at.get(&call.block) else {
347 continue;
348 };
349 let args: Vec<Value> = keep.iter().map(|&index| func[call.args][index]).collect();
350 let args = func.push_values(&args);
351 func.set_block_call(target, BlockCall { args, ..call });
352 }
353 }
354 for block in at.keys().copied().collect::<Vec<Block>>() {
355 func.retain_params(block, |param| !going.contains(¶m));
356 }
357}
358
359fn start_of_chain(func: &mut Func, first: Inst) -> Value {
365 let span = func.span(first);
366 let inst = func.create_inst(InstData::new(Opcode::MemEntry), &[Type::MEM], span);
367 func.insert_before(inst, first);
368 func[inst].results().next().expect("mem_entry produces one value")
369}
370
371fn thread(
378 func: &mut Func,
379 doms: &Dominators,
380 params: &HashMap<Block, Value>,
381 entry: Block,
382 start: Value,
383) -> HashMap<Block, Value> {
384 let mut forward: Vec<(Value, Value)> = Vec::new();
389 let mut ends = HashMap::new();
390 let mut stack = vec![(entry, start)];
391 while let Some((block, incoming)) = stack.pop() {
392 let mut current = params.get(&block).copied().unwrap_or(incoming);
393 for inst in func.insts(block).collect::<Vec<_>>() {
394 if !func[inst].opcode.touches_memory() {
395 continue;
396 }
397 let fresh = func.with_mem(inst, current);
398 func.insert_before(fresh, inst);
399 for (old, new) in func[inst].results().zip(func[fresh].results()) {
400 forward.push((old, new));
401 }
402 func.remove_inst(inst);
403 if let Some(next) = func.mem_out(fresh) {
404 current = next;
405 }
406 }
407 ends.insert(block, current);
408 stack.extend(doms.children(block).map(|child| (child, current)));
409 }
410
411 let forward: HashMap<Value, Value> = forward.into_iter().collect();
412 if !forward.is_empty() {
413 substitute(func, &forward);
414 }
415 ends
416}
417
418fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
420 let with = |value: Value| forward.get(&value).copied().unwrap_or(value);
421 for block in func.blocks().collect::<Vec<_>>() {
422 for inst in func.insts(block).collect::<Vec<_>>() {
423 let args = func[inst].args;
424 func.rewrite(args, with);
425 for call in func.successors(inst).collect::<Vec<_>>() {
426 func.rewrite(call.args, with);
427 }
428 }
429 }
430}
431
432fn pass_it_on(func: &mut Func, params: &HashMap<Block, Value>, ends: &HashMap<Block, Value>) {
434 for block in func.blocks().collect::<Vec<_>>() {
435 let Some(terminator) = func.terminator(block) else {
436 continue;
437 };
438 let Some(&value) = ends.get(&block) else {
439 continue;
440 };
441 for at in func.target_list(terminator).iter() {
442 let call = func[at];
443 if !params.contains_key(&call.block) {
444 continue;
445 }
446 let args = func.append_arg(call.args, value);
449 func.set_block_call(at, BlockCall { args, ..call });
450 }
451 }
452}
453
454fn iterated_frontier(cfg: &Cfg, doms: &Dominators, defs: &[Block]) -> HashSet<Block> {
457 let frontier = frontiers(cfg, doms);
458 let mut placed = HashSet::new();
459 let mut seen: HashSet<Block> = defs.iter().copied().collect();
460 let mut work: Vec<Block> = defs.to_vec();
461 while let Some(block) = work.pop() {
462 let Some(targets) = frontier.get(&block) else {
463 continue;
464 };
465 for &target in targets {
466 if placed.insert(target) && seen.insert(target) {
467 work.push(target);
468 }
469 }
470 }
471 placed
472}
473
474fn frontiers(cfg: &Cfg, doms: &Dominators) -> HashMap<Block, Vec<Block>> {
477 let mut frontier: HashMap<Block, Vec<Block>> = HashMap::new();
478 for block in cfg.reverse_postorder() {
479 let preds = cfg.predecessors(block);
480 if preds.len() < 2 {
481 continue;
482 }
483 let Some(top) = doms.immediate_dominator(block) else {
484 continue;
485 };
486 for &pred in preds {
487 let mut runner = pred;
488 while runner != top {
489 let at = frontier.entry(runner).or_default();
490 if !at.contains(&block) {
491 at.push(block);
492 }
493 let Some(next) = doms.immediate_dominator(runner) else {
494 break;
495 };
496 runner = next;
497 }
498 }
499 }
500 frontier
501}
502
503#[derive(Debug)]
508pub struct Walk<'a> {
509 func: &'a Func,
510 cfg: Cfg,
511 alias: Alias<'a>,
512 limit: u32,
513 counts: Counts,
514}
515
516impl<'a> Walk<'a> {
517 #[must_use]
519 pub fn new(func: &'a Func, outside: &'a Outside) -> Self {
520 Self::with(func, outside, Options::default(), MAX_ALIAS_QUERIES_PER_ACCESS)
521 }
522
523 #[must_use]
525 pub fn with(func: &'a Func, outside: &'a Outside, options: Options, limit: u32) -> Self {
526 Self {
527 func,
528 cfg: Cfg::new(func),
529 alias: Alias::with(func, outside, options),
530 limit,
531 counts: Counts::default(),
532 }
533 }
534
535 #[must_use]
537 pub const fn counts(&self) -> &Counts {
538 &self.counts
539 }
540
541 #[must_use]
543 pub const fn alias(&self) -> &Alias<'a> {
544 &self.alias
545 }
546
547 pub fn clobber(&mut self, load: Inst) -> Clobber {
553 self.clobber_with(load, &mut |_, _| Step::Stop)
554 }
555
556 pub fn clobber_with(
568 &mut self,
569 load: Inst,
570 translate: &mut dyn FnMut(&Access, Inst) -> Step,
571 ) -> Clobber {
572 let (Some(reference), Some(version)) = (self.alias.reads(load), self.func.mem_in(load))
573 else {
574 return Clobber::Unknown;
575 };
576 self.counts.walks += 1;
577 let mut budget = self.limit;
578 let mut seen = HashSet::new();
579 let answer = self.back(reference, version, &mut budget, &mut seen, translate);
580 answer.unwrap_or(Clobber::NoClobber)
583 }
584
585 fn back(
591 &mut self,
592 reference: Access,
593 version: Value,
594 budget: &mut u32,
595 seen: &mut HashSet<Value>,
596 translate: &mut dyn FnMut(&Access, Inst) -> Step,
597 ) -> Option<Clobber> {
598 if !seen.insert(version) {
599 return None;
600 }
601 match self.func[version].def {
602 Def::Param { block, index } => {
606 let mut answer = None;
607 for pred in self.cfg.predecessors(block).to_vec() {
608 let Some(terminator) = self.func.terminator(pred) else {
609 continue;
610 };
611 for call in self.func.successors(terminator).collect::<Vec<_>>() {
612 if call.block != block {
613 continue;
614 }
615 let Some(&incoming) = self.func[call.args].get(index as usize) else {
616 continue;
617 };
618 let one = self.back(reference, incoming, budget, seen, translate);
619 answer = combine(answer, one);
620 if answer == Some(Clobber::Unknown) {
621 return answer;
622 }
623 }
624 }
625 answer
626 }
627 Def::Result { inst, .. } => {
628 if self.func[inst].opcode == Opcode::MemEntry {
629 return Some(Clobber::NoClobber);
630 }
631 if *budget == 0 {
632 self.counts.exhausted += 1;
633 return Some(Clobber::Unknown);
634 }
635 *budget -= 1;
636 self.counts.steps += 1;
637 let past = match self.wrote(&reference, inst) {
638 None => reference,
639 Some(answer) => match translate(&reference, inst) {
640 Step::Stop => return Some(answer),
641 Step::Retry(next) => {
646 seen.clear();
647 next
648 }
649 },
650 };
651 let next = self.func.mem_in(inst)?;
652 self.back(past, next, budget, seen, translate)
653 }
654 }
655 }
656
657 fn wrote(&mut self, reference: &Access, inst: Inst) -> Option<Clobber> {
662 if reference.volatile || self.func[inst].flags.contains(Flags::VOLATILE) {
666 return Some(Clobber::Maybe(inst));
667 }
668 if self.ordered(inst) {
672 return Some(Clobber::Maybe(inst));
673 }
674 if let Some(write) = self.alias.writes(inst) {
675 return match self.alias.query(reference, &write) {
676 Answer::No(_) => None,
677 Answer::May => Some(self.extent(reference, &write, inst)),
678 };
679 }
680 match self.alias.clobbered_by(reference, inst) {
684 Answer::No(_) => None,
685 Answer::May => Some(Clobber::Maybe(inst)),
686 }
687 }
688
689 fn extent(&self, reference: &Access, write: &Access, inst: Inst) -> Clobber {
702 if reference.origin != write.origin {
703 return Clobber::Maybe(inst);
704 }
705 let (Some(want), Some(wrote)) = (reference.range(), write.range()) else {
706 return Clobber::Maybe(inst);
707 };
708 if want == wrote {
709 Clobber::Exact(inst)
710 } else if wrote.0 < want.1 && want.0 < wrote.1 {
711 Clobber::Partial(inst)
712 } else {
713 Clobber::Maybe(inst)
716 }
717 }
718
719 fn ordered(&self, inst: Inst) -> bool {
721 use rucc_ir::Extra;
722 let order = match self.func[inst].extra {
723 Extra::Mem(at) => self.func[at].order,
724 Extra::Rmw(_, at) => self.func[at].order,
725 Extra::Order(order) => order,
726 _ => return false,
727 };
728 order != MemOrder::NotAtomic
729 }
730}
731
732fn combine(a: Option<Clobber>, b: Option<Clobber>) -> Option<Clobber> {
739 match (a, b) {
740 (None, other) | (other, None) => other,
741 (Some(one), Some(other)) if one == other => Some(one),
742 _ => Some(Clobber::Unknown),
743 }
744}
745
746#[cfg(test)]
747mod tests {
748 use rucc_base::Interner;
749 use rucc_ir::{Builder, MemInfo, Module, Restrict, Signature, parse, verify_func};
750
751 use super::*;
752
753 fn read(text: &str) -> (Module, Interner) {
755 let mut names = Interner::new();
756 let module = parse(text, &mut names).expect("the text parses");
757 (module, names)
758 }
759
760 const HEADER: &str = "\
761; ModuleID = 'mem.c'
762; format 0
763target triple = \"x86_64-unknown-linux-gnu\"
764target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
765";
766
767 fn wrap(signature: &str, body: &str) -> String {
768 format!("{HEADER}\nfunc @f{signature}, linkage(external) {{\n{body}}}\n")
769 }
770
771 fn built(text: &str) -> (Module, bool) {
775 let (mut module, names) = read(text);
776 let id = module.funcs().next().expect("one function");
777 let changed = build(&mut module[id]);
778 if let Err(errors) = verify_func(&module, &module[id], &names) {
779 panic!("{errors:#?}");
780 }
781 (module, changed)
782 }
783
784 fn one(module: &Module) -> &Func {
785 &module[module.funcs().next().expect("one function")]
786 }
787
788 fn nth(func: &Func, opcode: Opcode, want: usize) -> Inst {
790 func.blocks()
791 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
792 .filter(|&inst| func[inst].opcode == opcode)
793 .nth(want)
794 .expect("that many of them")
795 }
796
797 #[test]
798 fn a_function_with_no_memory_in_it_gets_no_chain() {
799 let text = wrap(
800 "(i32) -> i32",
801 "block0(%0: i32):
802 %1 = add %0, %0
803 return %1
804",
805 );
806 let (module, changed) = built(&text);
807 assert!(!changed);
808 assert_eq!(one(&module).blocks().count(), 1);
809 }
810
811 #[test]
812 fn a_straight_line_is_threaded_in_order() {
813 let text = wrap(
814 "(ptr) -> i32",
815 "block0(%0: ptr):
816 %1 = iconst.i32 7
817 store %1 -> %0, align 4
818 %2 = load.i32 %0, align 4
819 return %2
820",
821 );
822 let (module, changed) = built(&text);
823 assert!(changed);
824 let func = one(&module);
825 let start = nth(func, Opcode::MemEntry, 0);
826 let store = nth(func, Opcode::Store, 0);
827 let load = nth(func, Opcode::Load, 0);
828 assert_eq!(func.mem_in(store), func.mem_out(start));
829 assert_eq!(func.mem_in(load), func.mem_out(store));
830 assert_eq!(func.mem_out(load), None);
831 }
832
833 #[test]
834 fn a_join_gets_a_memory_parameter_and_every_branch_passes_one() {
835 let text = wrap(
836 "(ptr, i1) -> i32",
837 "block0(%0: ptr, %1: i1):
838 br_if %1, block1, block2
839
840block1:
841 %2 = iconst.i32 7
842 store %2 -> %0, align 4
843 jump block3
844
845block2:
846 jump block3
847
848block3:
849 %3 = load.i32 %0, align 4
850 return %3
851",
852 );
853 let (module, _) = built(&text);
854 let func = one(&module);
855 let join = func.blocks().nth(3).expect("four blocks");
856 assert_eq!(func[join].params.len(), 1);
857 let param = func[join].params[0];
858 assert!(func[param].ty.is_mem());
859 assert_eq!(func.mem_in(nth(func, Opcode::Load, 0)), Some(param));
860 }
861
862 #[test]
863 fn a_block_that_only_reads_needs_no_parameter() {
864 let text = wrap(
865 "(ptr, i1) -> i32",
866 "block0(%0: ptr, %1: i1):
867 br_if %1, block1, block2
868
869block1:
870 %2 = load.i32 %0, align 4
871 jump block3
872
873block2:
874 jump block3
875
876block3:
877 %3 = load.i32 %0, align 4
878 return %3
879",
880 );
881 let (module, _) = built(&text);
882 let func = one(&module);
883 for block in func.blocks() {
886 assert!(func[block].params.iter().all(|¶m| !func[param].ty.is_mem()));
887 }
888 }
889
890 #[test]
891 fn every_arm_of_a_switch_passes_its_own_version_along() {
892 let text = wrap(
893 "(ptr, i32) -> i32",
894 "block0(%0: ptr, %1: i32):
895 switch %1, block1, [0 => block2, 1 => block3]
896
897block1:
898 %2 = iconst.i32 1
899 store %2 -> %0, align 4
900 jump block4
901
902block2:
903 %3 = iconst.i32 2
904 store %3 -> %0, align 4
905 jump block4
906
907block3:
908 jump block4
909
910block4:
911 %4 = load.i32 %0, align 4
912 return %4
913",
914 );
915 let (module, _) = built(&text);
916 let func = one(&module);
917 let join = func.blocks().nth(4).expect("five blocks");
918 let param = *func[join].params.last().expect("a parameter");
919 assert!(func[param].ty.is_mem());
920 for (arm, want) in [(1, Some(0)), (2, Some(1)), (3, None)] {
923 let block = func.blocks().nth(arm).expect("that block");
924 let jump = func.terminator(block).expect("a terminator");
925 let call = func.successors(jump).next().expect("one target");
926 let sent = *func[call.args].last().expect("an argument");
927 let expect = match want {
928 Some(store) => func.mem_out(nth(func, Opcode::Store, store)),
929 None => func.mem_out(nth(func, Opcode::MemEntry, 0)),
930 };
931 assert_eq!(Some(sent), expect, "arm {arm} passed the wrong version");
932 }
933 }
934
935 #[test]
936 fn a_function_with_a_block_nothing_reaches_is_left_alone() {
937 let text = wrap(
938 "(ptr) -> i32",
939 "block0(%0: ptr):
940 %1 = iconst.i32 7
941 store %1 -> %0, align 4
942 jump block2
943
944block1:
945 %2 = iconst.i32 9
946 store %2 -> %0, align 4
947 jump block2
948
949block2:
950 %3 = load.i32 %0, align 4
951 return %3
952",
953 );
954 let (mut module, _) = read(&text);
957 let id = module.funcs().next().expect("one function");
958 assert!(!build(&mut module[id]));
959 assert_eq!(module[id].blocks().filter(|&b| !module[id][b].params.is_empty()).count(), 1);
960 }
961
962 fn last_load(func: &Func) -> Inst {
964 func.blocks()
965 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
966 .filter(|&inst| func[inst].opcode == Opcode::Load)
967 .last()
968 .expect("a load")
969 }
970
971 fn walked(text: &str) -> (Clobber, Counts) {
973 let (module, changed) = built(text);
974 assert!(changed, "the function has memory in it");
975 let func = one(&module);
976 let outside = Outside::of(&module);
977 let mut walk = Walk::new(func, &outside);
978 let answer = walk.clobber(last_load(func));
979 (answer, *walk.counts())
980 }
981
982 #[test]
983 fn a_load_sees_the_store_before_it() {
984 let text = wrap(
985 "(ptr) -> i32",
986 "block0(%0: ptr):
987 %1 = iconst.i32 7
988 store %1 -> %0, align 4
989 %2 = load.i32 %0, align 4
990 return %2
991",
992 );
993 let (answer, counts) = walked(&text);
994 assert!(matches!(answer, Clobber::Exact(_)));
995 assert_eq!(counts.walks(), 1);
996 assert_eq!(counts.steps(), 1);
997 assert_eq!(counts.exhausted(), 0);
998 }
999
1000 #[test]
1001 fn a_load_walks_past_a_store_to_another_object() {
1002 let text = wrap(
1003 "() -> i32",
1004 "block0:
1005 %0 = alloca, size 8, align 8
1006 %1 = alloca, size 8, align 8
1007 %2 = iconst.i32 7
1008 store %2 -> %0, align 4
1009 %3 = load.i32 %1, align 4
1010 return %3
1011",
1012 );
1013 let (answer, counts) = walked(&text);
1014 assert_eq!(answer, Clobber::NoClobber);
1015 assert_eq!(counts.steps(), 1);
1017 }
1018
1019 #[test]
1020 fn a_load_of_one_byte_of_a_wider_store_is_partial() {
1021 let text = wrap(
1022 "() -> i8",
1023 "block0:
1024 %0 = alloca, size 8, align 8
1025 %1 = iconst.i32 7
1026 store %1 -> %0, align 4
1027 %2 = iconst.i64 1
1028 %3 = ptr_add %0, %2
1029 %4 = load.i8 %3, align 1
1030 return %4
1031",
1032 );
1033 let (answer, _) = walked(&text);
1034 assert!(matches!(answer, Clobber::Partial(_)), "{answer:?}");
1035 }
1036
1037 #[test]
1038 fn a_load_after_a_call_that_cannot_reach_it_walks_past_the_call() {
1039 let text = wrap(
1040 "() -> i32",
1041 "block0:
1042 %0 = alloca, size 8, align 8
1043 %1 = iconst.i32 7
1044 store %1 -> %0, align 4
1045 call @g() : ()
1046 %2 = load.i32 %0, align 4
1047 return %2
1048",
1049 );
1050 let (answer, _) = walked(&text);
1053 assert!(matches!(answer, Clobber::Exact(_)), "{answer:?}");
1054 }
1055
1056 #[test]
1057 fn a_load_after_a_call_that_could_have_the_address_sees_the_call() {
1058 let text = wrap(
1059 "(ptr) -> i32",
1060 "block0(%0: ptr):
1061 %1 = iconst.i32 7
1062 store %1 -> %0, align 4
1063 call @g() : ()
1064 %2 = load.i32 %0, align 4
1065 return %2
1066",
1067 );
1068 let (answer, _) = walked(&text);
1069 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1070 }
1071
1072 #[test]
1073 fn a_load_after_an_atomic_store_sees_it_whatever_it_wrote() {
1074 let text = wrap(
1075 "() -> i32",
1076 "block0:
1077 %0 = alloca, size 8, align 8
1078 %1 = alloca, size 8, align 8
1079 %2 = iconst.i32 7
1080 atomic_store %2 -> %0, align 4, release
1081 %3 = load.i32 %1, align 4
1082 return %3
1083",
1084 );
1085 let (answer, _) = walked(&text);
1088 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1089 }
1090
1091 #[test]
1092 fn a_load_after_a_volatile_store_sees_it_whatever_it_wrote() {
1093 let text = wrap(
1094 "() -> i32",
1095 "block0:
1096 %0 = alloca, size 8, align 8
1097 %1 = alloca, size 8, align 8
1098 %2 = iconst.i32 7
1099 store.volatile %2 -> %0, align 4
1100 %3 = load.i32 %1, align 4
1101 return %3
1102",
1103 );
1104 let (answer, _) = walked(&text);
1105 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1106 }
1107
1108 #[test]
1109 fn paths_that_disagree_are_unknown_rather_than_the_weaker_of_the_two() {
1110 let text = wrap(
1111 "(i1) -> i32",
1112 "block0(%0: i1):
1113 %1 = alloca, size 8, align 8
1114 br_if %0, block1, block2
1115
1116block1:
1117 %2 = iconst.i32 7
1118 store %2 -> %1, align 4
1119 jump block3
1120
1121block2:
1122 jump block3
1123
1124block3:
1125 %3 = load.i32 %1, align 4
1126 return %3
1127",
1128 );
1129 let (answer, _) = walked(&text);
1130 assert_eq!(answer, Clobber::Unknown);
1131 }
1132
1133 #[test]
1134 fn a_loop_that_writes_nothing_relevant_walks_out_of_it() {
1135 let text = wrap(
1136 "(i32) -> i32",
1137 "block0(%0: i32):
1138 %1 = alloca, size 8, align 8
1139 %2 = alloca, size 8, align 8
1140 %3 = iconst.i32 7
1141 store %3 -> %1, align 4
1142 jump block1(%0)
1143
1144block1(%4: i32):
1145 %5 = iconst.i32 1
1146 %6 = sub %4, %5
1147 store %5 -> %2, align 4
1148 %7 = icmp sgt %6, %5
1149 br_if %7, block1(%6), block2
1150
1151block2:
1152 %8 = load.i32 %1, align 4
1153 return %8
1154",
1155 );
1156 let (answer, counts) = walked(&text);
1160 assert!(matches!(answer, Clobber::Exact(_)), "{answer:?}");
1161 assert_eq!(counts.exhausted(), 0);
1162 }
1163
1164 #[test]
1165 fn a_budget_of_nothing_gives_unknown_and_says_so() {
1166 let text = wrap(
1167 "(ptr) -> i32",
1168 "block0(%0: ptr):
1169 %1 = iconst.i32 7
1170 store %1 -> %0, align 4
1171 %2 = load.i32 %0, align 4
1172 return %2
1173",
1174 );
1175 let (module, _) = built(&text);
1176 let func = one(&module);
1177 let load = nth(func, Opcode::Load, 0);
1178 let outside = Outside::of(&module);
1179 let mut walk = Walk::with(func, &outside, Options::default(), 0);
1180 assert_eq!(walk.clobber(load), Clobber::Unknown);
1181 assert_eq!(walk.counts().exhausted(), 1);
1182 }
1183
1184 #[test]
1185 fn translate_carries_the_walk_past_a_def_it_would_have_stopped_at() {
1186 let text = wrap(
1187 "(ptr) -> i32",
1188 "block0(%0: ptr):
1189 %1 = iconst.i32 7
1190 store %1 -> %0, align 4
1191 memcpy %0, %0, size 4, align 4
1192 %2 = load.i32 %0, align 4
1193 return %2
1194",
1195 );
1196 let (module, _) = built(&text);
1197 let func = one(&module);
1198 let load = nth(func, Opcode::Load, 0);
1199
1200 let outside = Outside::of(&module);
1202 let mut walk = Walk::new(func, &outside);
1203 let stopped_at = walk.clobber(load).inst().expect("something wrote it");
1204 assert_eq!(func[stopped_at].opcode, Opcode::Memcpy);
1205
1206 let mut walk = Walk::new(func, &outside);
1209 let mut seen = Vec::new();
1210 let answer = walk.clobber_with(load, &mut |reference, inst| {
1211 seen.push(func[inst].opcode);
1212 if func[inst].opcode == Opcode::Memcpy { Step::Retry(*reference) } else { Step::Stop }
1213 });
1214 assert_eq!(seen, [Opcode::Memcpy, Opcode::Store]);
1215 assert_eq!(answer.inst().map(|inst| func[inst].opcode), Some(Opcode::Store));
1216 }
1217
1218 #[test]
1219 fn building_twice_changes_nothing_the_second_time() {
1220 let text = wrap(
1221 "(ptr) -> i32",
1222 "block0(%0: ptr):
1223 %1 = load.i32 %0, align 4
1224 return %1
1225",
1226 );
1227 let (mut module, _) = read(&text);
1228 let id = module.funcs().next().expect("one function");
1229 let func = &mut module[id];
1230 assert!(build(func));
1231 let before = func.counts().insts;
1232 assert!(!build(func));
1233 assert_eq!(func.counts().insts, before);
1234 }
1235
1236 #[test]
1239 fn a_function_built_by_hand_threads_the_same_way() {
1240 let mut names = Interner::new();
1241 let i32_ = Type::int(32);
1242 let mut func = Func::new(
1243 names.intern("f"),
1244 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1245 );
1246 let entry = func.create_block();
1247 let addr = func.append_param(entry, Type::PTR);
1248 let info = MemInfo {
1249 size: 4,
1250 align: 4,
1251 order: MemOrder::NotAtomic,
1252 tbaa: None,
1253 owns: 0,
1254 restrict: Restrict::NONE,
1255 };
1256 let mut b = Builder::new(&mut func, entry);
1257 let seven = b.iconst(i32_, 7);
1258 b.store(seven, addr, info, Flags::NONE);
1259 let read = b.load(i32_, addr, info, Flags::NONE);
1260 b.ret(&[read]);
1261
1262 assert!(build(&mut func));
1263 let store = nth(&func, Opcode::Store, 0);
1264 let load = nth(&func, Opcode::Load, 0);
1265 assert_eq!(func.mem_in(load), func.mem_out(store));
1266 }
1267
1268 fn stripped(text: &str) -> (Module, bool) {
1272 let (mut module, names) = read(text);
1273 let id = module.funcs().next().expect("one function");
1274 build(&mut module[id]);
1275 if let Err(errors) = verify_func(&module, &module[id], &names) {
1276 panic!("after building: {errors:#?}");
1277 }
1278 let changed = strip(&mut module[id]);
1279 if let Err(errors) = verify_func(&module, &module[id], &names) {
1280 panic!("after stripping: {errors:#?}");
1281 }
1282 (module, changed)
1283 }
1284
1285 fn off(func: &Func) {
1287 for block in func.blocks() {
1288 assert!(
1289 func[block].params.iter().all(|¶m| !func[param].ty.is_mem()),
1290 "a block kept a memory parameter"
1291 );
1292 for inst in func.insts(block) {
1293 assert_ne!(
1294 func[inst].opcode,
1295 Opcode::MemEntry,
1296 "the start of the chain is still here"
1297 );
1298 assert!(!func.carries_mem(inst), "an instruction is still on the chain");
1299 }
1300 }
1301 }
1302
1303 #[test]
1304 fn a_straight_line_comes_off_the_chain_the_way_it_went_on() {
1305 let text = wrap(
1306 "(ptr) -> i32",
1307 "block0(%0: ptr):
1308 %1 = iconst.i32 7
1309 store %1 -> %0, align 4
1310 %2 = load.i32 %0, align 4
1311 return %2
1312",
1313 );
1314 let (module, changed) = stripped(&text);
1315 assert!(changed);
1316 let func = one(&module);
1317 off(func);
1318 let load = nth(func, Opcode::Load, 0);
1322 let param = func[func.entry().expect("an entry")].params[0];
1323 assert_eq!(func[func[load].args][0], param);
1324 let ret = nth(func, Opcode::Return, 0);
1325 assert_eq!(func[func[ret].args][0], func[load].results().next().expect("a result"));
1326 }
1327
1328 #[test]
1329 fn a_join_gives_its_memory_parameter_back_and_so_does_every_branch_to_it() {
1330 let text = wrap(
1331 "(ptr, i1) -> i32",
1332 "block0(%0: ptr, %1: i1):
1333 br_if %1, block1, block2
1334
1335block1:
1336 %2 = iconst.i32 7
1337 store %2 -> %0, align 4
1338 jump block3
1339
1340block2:
1341 jump block3
1342
1343block3:
1344 %3 = load.i32 %0, align 4
1345 return %3
1346",
1347 );
1348 let (module, changed) = stripped(&text);
1349 assert!(changed);
1350 let func = one(&module);
1351 off(func);
1352 let join = func.blocks().nth(3).expect("four blocks");
1353 assert!(func[join].params.is_empty(), "the join kept a parameter");
1354 for block in func.blocks() {
1355 let Some(terminator) = func.terminator(block) else { continue };
1356 for call in func.successors(terminator) {
1357 assert!(func[call.args].is_empty(), "a branch kept an argument");
1358 }
1359 }
1360 }
1361
1362 #[test]
1363 fn a_parameter_that_was_never_memory_keeps_its_place() {
1364 let text = wrap(
1367 "(ptr, i1) -> i32",
1368 "block0(%0: ptr, %1: i1):
1369 %2 = iconst.i32 7
1370 br_if %1, block1(%2), block2
1371
1372block1(%3: i32):
1373 store %3 -> %0, align 4
1374 jump block3
1375
1376block2:
1377 jump block3
1378
1379block3:
1380 %4 = load.i32 %0, align 4
1381 return %4
1382",
1383 );
1384 let (module, _) = stripped(&text);
1385 let func = one(&module);
1386 off(func);
1387 let arm = func.blocks().nth(1).expect("four blocks");
1388 assert_eq!(func[arm].params.len(), 1);
1389 let param = func[arm].params[0];
1390 assert_eq!(func[param].ty, Type::int(32));
1391 let store = nth(func, Opcode::Store, 0);
1392 assert_eq!(func[func[store].args][0], param, "the store lost the value it writes");
1393 }
1394
1395 #[test]
1396 fn a_function_that_was_never_on_the_chain_is_left_alone() {
1397 let text = wrap(
1398 "(i32) -> i32",
1399 "block0(%0: i32):
1400 %1 = add %0, %0
1401 return %1
1402",
1403 );
1404 let (mut module, names) = read(&text);
1405 let id = module.funcs().next().expect("one function");
1406 assert!(!strip(&mut module[id]));
1407 if let Err(errors) = verify_func(&module, &module[id], &names) {
1408 panic!("{errors:#?}");
1409 }
1410 }
1411
1412 #[test]
1413 fn a_call_that_returns_something_keeps_it() {
1414 let text = format!(
1417 "{HEADER}\nfunc @f() -> i32, linkage(external) {{\nblock0:\n %0 = call @g() : () -> \
1418 i32\n return %0\n}}\n"
1419 );
1420 let (module, changed) = stripped(&text);
1421 assert!(changed);
1422 let func = one(&module);
1423 off(func);
1424 let call = nth(func, Opcode::Call, 0);
1425 let ret = nth(func, Opcode::Return, 0);
1426 assert_eq!(func[call].results().count(), 1);
1427 assert_eq!(func[func[ret].args][0], func[call].results().next().expect("a result"));
1428 }
1429}