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]
545 pub fn knowing(mut self, summaries: &'a crate::modref::Summaries) -> Self {
546 self.alias = self.alias.knowing(summaries);
547 self
548 }
549
550 #[must_use]
552 pub const fn alias(&self) -> &Alias<'a> {
553 &self.alias
554 }
555
556 pub fn clobber(&mut self, load: Inst) -> Clobber {
562 self.clobber_with(load, &mut |_, _| Step::Stop)
563 }
564
565 pub fn clobber_with(
577 &mut self,
578 load: Inst,
579 translate: &mut dyn FnMut(&Access, Inst) -> Step,
580 ) -> Clobber {
581 let (Some(reference), Some(version)) = (self.alias.reads(load), self.func.mem_in(load))
582 else {
583 return Clobber::Unknown;
584 };
585 self.counts.walks += 1;
586 let mut budget = self.limit;
587 let mut seen = HashSet::new();
588 let answer = self.back(reference, version, &mut budget, &mut seen, translate);
589 answer.unwrap_or(Clobber::NoClobber)
592 }
593
594 fn back(
600 &mut self,
601 reference: Access,
602 version: Value,
603 budget: &mut u32,
604 seen: &mut HashSet<Value>,
605 translate: &mut dyn FnMut(&Access, Inst) -> Step,
606 ) -> Option<Clobber> {
607 if !seen.insert(version) {
608 return None;
609 }
610 match self.func[version].def {
611 Def::Param { block, index } => {
615 let mut answer = None;
616 for pred in self.cfg.predecessors(block).to_vec() {
617 let Some(terminator) = self.func.terminator(pred) else {
618 continue;
619 };
620 for call in self.func.successors(terminator).collect::<Vec<_>>() {
621 if call.block != block {
622 continue;
623 }
624 let Some(&incoming) = self.func[call.args].get(index as usize) else {
625 continue;
626 };
627 let one = self.back(reference, incoming, budget, seen, translate);
628 answer = combine(answer, one);
629 if answer == Some(Clobber::Unknown) {
630 return answer;
631 }
632 }
633 }
634 answer
635 }
636 Def::Result { inst, .. } => {
637 if self.func[inst].opcode == Opcode::MemEntry {
638 return Some(Clobber::NoClobber);
639 }
640 if *budget == 0 {
641 self.counts.exhausted += 1;
642 return Some(Clobber::Unknown);
643 }
644 *budget -= 1;
645 self.counts.steps += 1;
646 let past = match self.wrote(&reference, inst) {
647 None => reference,
648 Some(answer) => match translate(&reference, inst) {
649 Step::Stop => return Some(answer),
650 Step::Retry(next) => {
661 let before = self.func.mem_in(inst)?;
662 let mut fresh = HashSet::new();
663 return self.back(next, before, budget, &mut fresh, translate);
664 }
665 },
666 };
667 let next = self.func.mem_in(inst)?;
668 self.back(past, next, budget, seen, translate)
669 }
670 }
671 }
672
673 fn wrote(&mut self, reference: &Access, inst: Inst) -> Option<Clobber> {
678 if reference.volatile || self.func[inst].flags.contains(Flags::VOLATILE) {
682 return Some(Clobber::Maybe(inst));
683 }
684 if self.ordered(inst) {
688 return Some(Clobber::Maybe(inst));
689 }
690 if let Some(write) = self.alias.writes(inst) {
691 return match self.alias.query(reference, &write) {
692 Answer::No(_) => None,
693 Answer::May => Some(self.extent(reference, &write, inst)),
694 };
695 }
696 match self.alias.clobbered_by(reference, inst) {
700 Answer::No(_) => None,
701 Answer::May => Some(Clobber::Maybe(inst)),
702 }
703 }
704
705 fn extent(&self, reference: &Access, write: &Access, inst: Inst) -> Clobber {
718 if reference.origin != write.origin {
719 return Clobber::Maybe(inst);
720 }
721 let (Some(want), Some(wrote)) = (reference.range(), write.range()) else {
722 return Clobber::Maybe(inst);
723 };
724 if want == wrote {
725 Clobber::Exact(inst)
726 } else if wrote.0 < want.1 && want.0 < wrote.1 {
727 Clobber::Partial(inst)
728 } else {
729 Clobber::Maybe(inst)
732 }
733 }
734
735 fn ordered(&self, inst: Inst) -> bool {
737 use rucc_ir::Extra;
738 let order = match self.func[inst].extra {
739 Extra::Mem(at) => self.func[at].order,
740 Extra::Rmw(_, at) => self.func[at].order,
741 Extra::Order(order) => order,
742 _ => return false,
743 };
744 order != MemOrder::NotAtomic
745 }
746}
747
748fn combine(a: Option<Clobber>, b: Option<Clobber>) -> Option<Clobber> {
755 match (a, b) {
756 (None, other) | (other, None) => other,
757 (Some(one), Some(other)) if one == other => Some(one),
758 _ => Some(Clobber::Unknown),
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use rucc_base::Interner;
765 use rucc_ir::{Builder, MemInfo, Module, Restrict, Signature, parse, verify_func};
766
767 use super::*;
768
769 fn read(text: &str) -> (Module, Interner) {
771 let mut names = Interner::new();
772 let module = parse(text, &mut names).expect("the text parses");
773 (module, names)
774 }
775
776 const HEADER: &str = "\
777; ModuleID = 'mem.c'
778; format 0
779target triple = \"x86_64-unknown-linux-gnu\"
780target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
781";
782
783 fn wrap(signature: &str, body: &str) -> String {
784 format!("{HEADER}\nfunc @f{signature}, linkage(external) {{\n{body}}}\n")
785 }
786
787 fn built(text: &str) -> (Module, bool) {
791 let (mut module, names) = read(text);
792 let id = module.funcs().next().expect("one function");
793 let changed = build(&mut module[id]);
794 if let Err(errors) = verify_func(&module, &module[id], &names) {
795 panic!("{errors:#?}");
796 }
797 (module, changed)
798 }
799
800 fn one(module: &Module) -> &Func {
801 &module[module.funcs().next().expect("one function")]
802 }
803
804 fn nth(func: &Func, opcode: Opcode, want: usize) -> Inst {
806 func.blocks()
807 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
808 .filter(|&inst| func[inst].opcode == opcode)
809 .nth(want)
810 .expect("that many of them")
811 }
812
813 #[test]
814 fn a_function_with_no_memory_in_it_gets_no_chain() {
815 let text = wrap(
816 "(i32) -> i32",
817 "block0(%0: i32):
818 %1 = add %0, %0
819 return %1
820",
821 );
822 let (module, changed) = built(&text);
823 assert!(!changed);
824 assert_eq!(one(&module).blocks().count(), 1);
825 }
826
827 #[test]
828 fn a_straight_line_is_threaded_in_order() {
829 let text = wrap(
830 "(ptr) -> i32",
831 "block0(%0: ptr):
832 %1 = iconst.i32 7
833 store %1 -> %0, align 4
834 %2 = load.i32 %0, align 4
835 return %2
836",
837 );
838 let (module, changed) = built(&text);
839 assert!(changed);
840 let func = one(&module);
841 let start = nth(func, Opcode::MemEntry, 0);
842 let store = nth(func, Opcode::Store, 0);
843 let load = nth(func, Opcode::Load, 0);
844 assert_eq!(func.mem_in(store), func.mem_out(start));
845 assert_eq!(func.mem_in(load), func.mem_out(store));
846 assert_eq!(func.mem_out(load), None);
847 }
848
849 #[test]
850 fn a_join_gets_a_memory_parameter_and_every_branch_passes_one() {
851 let text = wrap(
852 "(ptr, i1) -> i32",
853 "block0(%0: ptr, %1: i1):
854 br_if %1, block1, block2
855
856block1:
857 %2 = iconst.i32 7
858 store %2 -> %0, align 4
859 jump block3
860
861block2:
862 jump block3
863
864block3:
865 %3 = load.i32 %0, align 4
866 return %3
867",
868 );
869 let (module, _) = built(&text);
870 let func = one(&module);
871 let join = func.blocks().nth(3).expect("four blocks");
872 assert_eq!(func[join].params.len(), 1);
873 let param = func[join].params[0];
874 assert!(func[param].ty.is_mem());
875 assert_eq!(func.mem_in(nth(func, Opcode::Load, 0)), Some(param));
876 }
877
878 #[test]
879 fn a_block_that_only_reads_needs_no_parameter() {
880 let text = wrap(
881 "(ptr, i1) -> i32",
882 "block0(%0: ptr, %1: i1):
883 br_if %1, block1, block2
884
885block1:
886 %2 = load.i32 %0, align 4
887 jump block3
888
889block2:
890 jump block3
891
892block3:
893 %3 = load.i32 %0, align 4
894 return %3
895",
896 );
897 let (module, _) = built(&text);
898 let func = one(&module);
899 for block in func.blocks() {
902 assert!(func[block].params.iter().all(|¶m| !func[param].ty.is_mem()));
903 }
904 }
905
906 #[test]
907 fn every_arm_of_a_switch_passes_its_own_version_along() {
908 let text = wrap(
909 "(ptr, i32) -> i32",
910 "block0(%0: ptr, %1: i32):
911 switch %1, block1, [0 => block2, 1 => block3]
912
913block1:
914 %2 = iconst.i32 1
915 store %2 -> %0, align 4
916 jump block4
917
918block2:
919 %3 = iconst.i32 2
920 store %3 -> %0, align 4
921 jump block4
922
923block3:
924 jump block4
925
926block4:
927 %4 = load.i32 %0, align 4
928 return %4
929",
930 );
931 let (module, _) = built(&text);
932 let func = one(&module);
933 let join = func.blocks().nth(4).expect("five blocks");
934 let param = *func[join].params.last().expect("a parameter");
935 assert!(func[param].ty.is_mem());
936 for (arm, want) in [(1, Some(0)), (2, Some(1)), (3, None)] {
939 let block = func.blocks().nth(arm).expect("that block");
940 let jump = func.terminator(block).expect("a terminator");
941 let call = func.successors(jump).next().expect("one target");
942 let sent = *func[call.args].last().expect("an argument");
943 let expect = match want {
944 Some(store) => func.mem_out(nth(func, Opcode::Store, store)),
945 None => func.mem_out(nth(func, Opcode::MemEntry, 0)),
946 };
947 assert_eq!(Some(sent), expect, "arm {arm} passed the wrong version");
948 }
949 }
950
951 #[test]
952 fn a_function_with_a_block_nothing_reaches_is_left_alone() {
953 let text = wrap(
954 "(ptr) -> i32",
955 "block0(%0: ptr):
956 %1 = iconst.i32 7
957 store %1 -> %0, align 4
958 jump block2
959
960block1:
961 %2 = iconst.i32 9
962 store %2 -> %0, align 4
963 jump block2
964
965block2:
966 %3 = load.i32 %0, align 4
967 return %3
968",
969 );
970 let (mut module, _) = read(&text);
973 let id = module.funcs().next().expect("one function");
974 assert!(!build(&mut module[id]));
975 assert_eq!(module[id].blocks().filter(|&b| !module[id][b].params.is_empty()).count(), 1);
976 }
977
978 fn last_load(func: &Func) -> Inst {
980 func.blocks()
981 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
982 .filter(|&inst| func[inst].opcode == Opcode::Load)
983 .last()
984 .expect("a load")
985 }
986
987 fn walked(text: &str) -> (Clobber, Counts) {
989 let (module, changed) = built(text);
990 assert!(changed, "the function has memory in it");
991 let func = one(&module);
992 let outside = Outside::of(&module);
993 let mut walk = Walk::new(func, &outside);
994 let answer = walk.clobber(last_load(func));
995 (answer, *walk.counts())
996 }
997
998 #[test]
999 fn a_load_sees_the_store_before_it() {
1000 let text = wrap(
1001 "(ptr) -> i32",
1002 "block0(%0: ptr):
1003 %1 = iconst.i32 7
1004 store %1 -> %0, align 4
1005 %2 = load.i32 %0, align 4
1006 return %2
1007",
1008 );
1009 let (answer, counts) = walked(&text);
1010 assert!(matches!(answer, Clobber::Exact(_)));
1011 assert_eq!(counts.walks(), 1);
1012 assert_eq!(counts.steps(), 1);
1013 assert_eq!(counts.exhausted(), 0);
1014 }
1015
1016 #[test]
1017 fn a_load_walks_past_a_store_to_another_object() {
1018 let text = wrap(
1019 "() -> i32",
1020 "block0:
1021 %0 = alloca, size 8, align 8
1022 %1 = alloca, size 8, align 8
1023 %2 = iconst.i32 7
1024 store %2 -> %0, align 4
1025 %3 = load.i32 %1, align 4
1026 return %3
1027",
1028 );
1029 let (answer, counts) = walked(&text);
1030 assert_eq!(answer, Clobber::NoClobber);
1031 assert_eq!(counts.steps(), 1);
1033 }
1034
1035 #[test]
1036 fn a_load_of_one_byte_of_a_wider_store_is_partial() {
1037 let text = wrap(
1038 "() -> i8",
1039 "block0:
1040 %0 = alloca, size 8, align 8
1041 %1 = iconst.i32 7
1042 store %1 -> %0, align 4
1043 %2 = iconst.i64 1
1044 %3 = ptr_add %0, %2
1045 %4 = load.i8 %3, align 1
1046 return %4
1047",
1048 );
1049 let (answer, _) = walked(&text);
1050 assert!(matches!(answer, Clobber::Partial(_)), "{answer:?}");
1051 }
1052
1053 #[test]
1054 fn a_load_after_a_call_that_cannot_reach_it_walks_past_the_call() {
1055 let text = wrap(
1056 "() -> i32",
1057 "block0:
1058 %0 = alloca, size 8, align 8
1059 %1 = iconst.i32 7
1060 store %1 -> %0, align 4
1061 call @g() : ()
1062 %2 = load.i32 %0, align 4
1063 return %2
1064",
1065 );
1066 let (answer, _) = walked(&text);
1069 assert!(matches!(answer, Clobber::Exact(_)), "{answer:?}");
1070 }
1071
1072 #[test]
1073 fn a_load_after_a_call_that_could_have_the_address_sees_the_call() {
1074 let text = wrap(
1075 "(ptr) -> i32",
1076 "block0(%0: ptr):
1077 %1 = iconst.i32 7
1078 store %1 -> %0, align 4
1079 call @g() : ()
1080 %2 = load.i32 %0, align 4
1081 return %2
1082",
1083 );
1084 let (answer, _) = walked(&text);
1085 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1086 }
1087
1088 #[test]
1089 fn a_load_after_an_atomic_store_sees_it_whatever_it_wrote() {
1090 let text = wrap(
1091 "() -> i32",
1092 "block0:
1093 %0 = alloca, size 8, align 8
1094 %1 = alloca, size 8, align 8
1095 %2 = iconst.i32 7
1096 atomic_store %2 -> %0, align 4, release
1097 %3 = load.i32 %1, align 4
1098 return %3
1099",
1100 );
1101 let (answer, _) = walked(&text);
1104 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1105 }
1106
1107 #[test]
1108 fn a_load_after_a_volatile_store_sees_it_whatever_it_wrote() {
1109 let text = wrap(
1110 "() -> i32",
1111 "block0:
1112 %0 = alloca, size 8, align 8
1113 %1 = alloca, size 8, align 8
1114 %2 = iconst.i32 7
1115 store.volatile %2 -> %0, align 4
1116 %3 = load.i32 %1, align 4
1117 return %3
1118",
1119 );
1120 let (answer, _) = walked(&text);
1121 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1122 }
1123
1124 #[test]
1125 fn paths_that_disagree_are_unknown_rather_than_the_weaker_of_the_two() {
1126 let text = wrap(
1127 "(i1) -> i32",
1128 "block0(%0: i1):
1129 %1 = alloca, size 8, align 8
1130 br_if %0, block1, block2
1131
1132block1:
1133 %2 = iconst.i32 7
1134 store %2 -> %1, align 4
1135 jump block3
1136
1137block2:
1138 jump block3
1139
1140block3:
1141 %3 = load.i32 %1, align 4
1142 return %3
1143",
1144 );
1145 let (answer, _) = walked(&text);
1146 assert_eq!(answer, Clobber::Unknown);
1147 }
1148
1149 #[test]
1150 fn a_loop_that_writes_nothing_relevant_walks_out_of_it() {
1151 let text = wrap(
1152 "(i32) -> i32",
1153 "block0(%0: i32):
1154 %1 = alloca, size 8, align 8
1155 %2 = alloca, size 8, align 8
1156 %3 = iconst.i32 7
1157 store %3 -> %1, align 4
1158 jump block1(%0)
1159
1160block1(%4: i32):
1161 %5 = iconst.i32 1
1162 %6 = sub %4, %5
1163 store %5 -> %2, align 4
1164 %7 = icmp sgt %6, %5
1165 br_if %7, block1(%6), block2
1166
1167block2:
1168 %8 = load.i32 %1, align 4
1169 return %8
1170",
1171 );
1172 let (answer, counts) = walked(&text);
1176 assert!(matches!(answer, Clobber::Exact(_)), "{answer:?}");
1177 assert_eq!(counts.exhausted(), 0);
1178 }
1179
1180 #[test]
1181 fn a_budget_of_nothing_gives_unknown_and_says_so() {
1182 let text = wrap(
1183 "(ptr) -> i32",
1184 "block0(%0: ptr):
1185 %1 = iconst.i32 7
1186 store %1 -> %0, align 4
1187 %2 = load.i32 %0, align 4
1188 return %2
1189",
1190 );
1191 let (module, _) = built(&text);
1192 let func = one(&module);
1193 let load = nth(func, Opcode::Load, 0);
1194 let outside = Outside::of(&module);
1195 let mut walk = Walk::with(func, &outside, Options::default(), 0);
1196 assert_eq!(walk.clobber(load), Clobber::Unknown);
1197 assert_eq!(walk.counts().exhausted(), 1);
1198 }
1199
1200 #[test]
1201 fn translate_carries_the_walk_past_a_def_it_would_have_stopped_at() {
1202 let text = wrap(
1203 "(ptr) -> i32",
1204 "block0(%0: ptr):
1205 %1 = iconst.i32 7
1206 store %1 -> %0, align 4
1207 memcpy %0, %0, size 4, align 4
1208 %2 = load.i32 %0, align 4
1209 return %2
1210",
1211 );
1212 let (module, _) = built(&text);
1213 let func = one(&module);
1214 let load = nth(func, Opcode::Load, 0);
1215
1216 let outside = Outside::of(&module);
1218 let mut walk = Walk::new(func, &outside);
1219 let stopped_at = walk.clobber(load).inst().expect("something wrote it");
1220 assert_eq!(func[stopped_at].opcode, Opcode::Memcpy);
1221
1222 let mut walk = Walk::new(func, &outside);
1225 let mut seen = Vec::new();
1226 let answer = walk.clobber_with(load, &mut |reference, inst| {
1227 seen.push(func[inst].opcode);
1228 if func[inst].opcode == Opcode::Memcpy { Step::Retry(*reference) } else { Step::Stop }
1229 });
1230 assert_eq!(seen, [Opcode::Memcpy, Opcode::Store]);
1231 assert_eq!(answer.inst().map(|inst| func[inst].opcode), Some(Opcode::Store));
1232 }
1233
1234 #[test]
1235 fn building_twice_changes_nothing_the_second_time() {
1236 let text = wrap(
1237 "(ptr) -> i32",
1238 "block0(%0: ptr):
1239 %1 = load.i32 %0, align 4
1240 return %1
1241",
1242 );
1243 let (mut module, _) = read(&text);
1244 let id = module.funcs().next().expect("one function");
1245 let func = &mut module[id];
1246 assert!(build(func));
1247 let before = func.counts().insts;
1248 assert!(!build(func));
1249 assert_eq!(func.counts().insts, before);
1250 }
1251
1252 #[test]
1255 fn a_function_built_by_hand_threads_the_same_way() {
1256 let mut names = Interner::new();
1257 let i32_ = Type::int(32);
1258 let mut func = Func::new(
1259 names.intern("f"),
1260 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1261 );
1262 let entry = func.create_block();
1263 let addr = func.append_param(entry, Type::PTR);
1264 let info = MemInfo {
1265 size: 4,
1266 align: 4,
1267 order: MemOrder::NotAtomic,
1268 tbaa: None,
1269 owns: 0,
1270 restrict: Restrict::NONE,
1271 };
1272 let mut b = Builder::new(&mut func, entry);
1273 let seven = b.iconst(i32_, 7);
1274 b.store(seven, addr, info, Flags::NONE);
1275 let read = b.load(i32_, addr, info, Flags::NONE);
1276 b.ret(&[read]);
1277
1278 assert!(build(&mut func));
1279 let store = nth(&func, Opcode::Store, 0);
1280 let load = nth(&func, Opcode::Load, 0);
1281 assert_eq!(func.mem_in(load), func.mem_out(store));
1282 }
1283
1284 fn stripped(text: &str) -> (Module, bool) {
1288 let (mut module, names) = read(text);
1289 let id = module.funcs().next().expect("one function");
1290 build(&mut module[id]);
1291 if let Err(errors) = verify_func(&module, &module[id], &names) {
1292 panic!("after building: {errors:#?}");
1293 }
1294 let changed = strip(&mut module[id]);
1295 if let Err(errors) = verify_func(&module, &module[id], &names) {
1296 panic!("after stripping: {errors:#?}");
1297 }
1298 (module, changed)
1299 }
1300
1301 fn off(func: &Func) {
1303 for block in func.blocks() {
1304 assert!(
1305 func[block].params.iter().all(|¶m| !func[param].ty.is_mem()),
1306 "a block kept a memory parameter"
1307 );
1308 for inst in func.insts(block) {
1309 assert_ne!(
1310 func[inst].opcode,
1311 Opcode::MemEntry,
1312 "the start of the chain is still here"
1313 );
1314 assert!(!func.carries_mem(inst), "an instruction is still on the chain");
1315 }
1316 }
1317 }
1318
1319 #[test]
1320 fn a_straight_line_comes_off_the_chain_the_way_it_went_on() {
1321 let text = wrap(
1322 "(ptr) -> i32",
1323 "block0(%0: ptr):
1324 %1 = iconst.i32 7
1325 store %1 -> %0, align 4
1326 %2 = load.i32 %0, align 4
1327 return %2
1328",
1329 );
1330 let (module, changed) = stripped(&text);
1331 assert!(changed);
1332 let func = one(&module);
1333 off(func);
1334 let load = nth(func, Opcode::Load, 0);
1338 let param = func[func.entry().expect("an entry")].params[0];
1339 assert_eq!(func[func[load].args][0], param);
1340 let ret = nth(func, Opcode::Return, 0);
1341 assert_eq!(func[func[ret].args][0], func[load].results().next().expect("a result"));
1342 }
1343
1344 #[test]
1345 fn a_join_gives_its_memory_parameter_back_and_so_does_every_branch_to_it() {
1346 let text = wrap(
1347 "(ptr, i1) -> i32",
1348 "block0(%0: ptr, %1: i1):
1349 br_if %1, block1, block2
1350
1351block1:
1352 %2 = iconst.i32 7
1353 store %2 -> %0, align 4
1354 jump block3
1355
1356block2:
1357 jump block3
1358
1359block3:
1360 %3 = load.i32 %0, align 4
1361 return %3
1362",
1363 );
1364 let (module, changed) = stripped(&text);
1365 assert!(changed);
1366 let func = one(&module);
1367 off(func);
1368 let join = func.blocks().nth(3).expect("four blocks");
1369 assert!(func[join].params.is_empty(), "the join kept a parameter");
1370 for block in func.blocks() {
1371 let Some(terminator) = func.terminator(block) else { continue };
1372 for call in func.successors(terminator) {
1373 assert!(func[call.args].is_empty(), "a branch kept an argument");
1374 }
1375 }
1376 }
1377
1378 #[test]
1379 fn a_parameter_that_was_never_memory_keeps_its_place() {
1380 let text = wrap(
1383 "(ptr, i1) -> i32",
1384 "block0(%0: ptr, %1: i1):
1385 %2 = iconst.i32 7
1386 br_if %1, block1(%2), block2
1387
1388block1(%3: i32):
1389 store %3 -> %0, align 4
1390 jump block3
1391
1392block2:
1393 jump block3
1394
1395block3:
1396 %4 = load.i32 %0, align 4
1397 return %4
1398",
1399 );
1400 let (module, _) = stripped(&text);
1401 let func = one(&module);
1402 off(func);
1403 let arm = func.blocks().nth(1).expect("four blocks");
1404 assert_eq!(func[arm].params.len(), 1);
1405 let param = func[arm].params[0];
1406 assert_eq!(func[param].ty, Type::int(32));
1407 let store = nth(func, Opcode::Store, 0);
1408 assert_eq!(func[func[store].args][0], param, "the store lost the value it writes");
1409 }
1410
1411 #[test]
1412 fn a_function_that_was_never_on_the_chain_is_left_alone() {
1413 let text = wrap(
1414 "(i32) -> i32",
1415 "block0(%0: i32):
1416 %1 = add %0, %0
1417 return %1
1418",
1419 );
1420 let (mut module, names) = read(&text);
1421 let id = module.funcs().next().expect("one function");
1422 assert!(!strip(&mut module[id]));
1423 if let Err(errors) = verify_func(&module, &module[id], &names) {
1424 panic!("{errors:#?}");
1425 }
1426 }
1427
1428 #[test]
1429 fn a_call_that_returns_something_keeps_it() {
1430 let text = format!(
1433 "{HEADER}\nfunc @f() -> i32, linkage(external) {{\nblock0:\n %0 = call @g() : () -> \
1434 i32\n return %0\n}}\n"
1435 );
1436 let (module, changed) = stripped(&text);
1437 assert!(changed);
1438 let func = one(&module);
1439 off(func);
1440 let call = nth(func, Opcode::Call, 0);
1441 let ret = nth(func, Opcode::Return, 0);
1442 assert_eq!(func[call].results().count(), 1);
1443 assert_eq!(func[func[ret].args][0], func[call].results().next().expect("a result"));
1444 }
1445}