1use std::collections::{HashMap, HashSet};
91
92use rucc_ir::{Block, BlockCall, Def, Flags, Func, Inst, InstData, MemOrder, Opcode, Type, Value};
93
94use crate::alias::{Access, Alias, Answer, Options};
95use crate::cfg::Cfg;
96use crate::dom::Dominators;
97use crate::outside::Outside;
98
99pub const MAX_ALIAS_QUERIES_PER_ACCESS: u32 = 1000;
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub enum Clobber {
113 Exact(Inst),
118 Partial(Inst),
124 Maybe(Inst),
126 NoClobber,
128 Unknown,
130}
131
132impl Clobber {
133 #[must_use]
135 pub const fn inst(self) -> Option<Inst> {
136 match self {
137 Self::Exact(inst) | Self::Partial(inst) | Self::Maybe(inst) => Some(inst),
138 Self::NoClobber | Self::Unknown => None,
139 }
140 }
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
149pub enum Step {
150 Stop,
152 Retry(Access),
154}
155
156#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
163pub struct Counts {
164 walks: u64,
165 steps: u64,
166 exhausted: u64,
167 rewritten: u64,
168}
169
170impl Counts {
171 #[must_use]
173 pub const fn walks(&self) -> u64 {
174 self.walks
175 }
176
177 #[must_use]
179 pub const fn steps(&self) -> u64 {
180 self.steps
181 }
182
183 #[must_use]
188 pub const fn exhausted(&self) -> u64 {
189 self.exhausted
190 }
191
192 #[must_use]
198 pub const fn rewritten(&self) -> u64 {
199 self.rewritten
200 }
201}
202
203pub fn build(func: &mut Func) -> bool {
216 let Some(entry) = func.entry() else {
217 return false;
218 };
219 let cfg = Cfg::new(func);
220 let doms = Dominators::new(&cfg);
221
222 let mut defs = vec![entry];
224 let mut any = false;
225 for block in func.blocks() {
226 if !cfg.reaches(block) {
231 return false;
232 }
233 let mut writes = false;
234 for inst in func.insts(block) {
235 if func.carries_mem(inst) {
236 return false;
237 }
238 let opcode = func[inst].opcode;
239 any |= opcode.touches_memory();
240 writes |= opcode.writes_memory();
241 }
242 if writes && block != entry {
243 defs.push(block);
244 }
245 }
246 let Some(first) = func.insts(entry).next() else {
249 return false;
250 };
251 if !any {
252 return false;
253 }
254
255 let joins = iterated_frontier(&cfg, &doms, &defs);
256 let mut params = HashMap::new();
257 for block in func.blocks().collect::<Vec<_>>() {
258 if joins.contains(&block) {
259 params.insert(block, func.append_param(block, Type::MEM));
260 }
261 }
262
263 let start = start_of_chain(func, first);
264 let ends = thread(func, &doms, ¶ms, entry, start);
265 pass_it_on(func, ¶ms, &ends);
266 true
267}
268
269pub fn strip(func: &mut Func) -> bool {
290 let mut forward: Vec<(Value, Value)> = Vec::new();
291 let mut gone: Vec<Inst> = Vec::new();
292 let mut entry = None;
293 for block in func.blocks().collect::<Vec<Block>>() {
294 for inst in func.insts(block).collect::<Vec<Inst>>() {
295 if func[inst].opcode == Opcode::MemEntry {
296 entry = Some(inst);
297 continue;
298 }
299 if !func.carries_mem(inst) {
300 continue;
301 }
302 let bare = func.without_mem(inst);
303 func.insert_before(bare, inst);
304 for (old, new) in func[inst].results().zip(func[bare].results()) {
308 forward.push((old, new));
309 }
310 gone.push(inst);
311 }
312 }
313 if entry.is_none() && gone.is_empty() {
314 return false;
315 }
316 for inst in gone {
317 func.remove_inst(inst);
318 }
319 let forward: HashMap<Value, Value> = forward.into_iter().collect();
320 if !forward.is_empty() {
321 substitute(func, &forward);
322 }
323 drop_params(func);
324 if let Some(inst) = entry {
325 func.remove_inst(inst);
326 }
327 true
328}
329
330fn drop_params(func: &mut Func) {
338 let mut at: HashMap<Block, Vec<usize>> = HashMap::new();
339 let mut going: HashSet<Value> = HashSet::new();
340 for block in func.blocks().collect::<Vec<Block>>() {
341 let mut keep = Vec::new();
342 for (index, ¶m) in func[block].params.iter().enumerate() {
343 if func[param].ty.is_mem() {
344 going.insert(param);
345 } else {
346 keep.push(index);
347 }
348 }
349 if keep.len() != func[block].params.len() {
350 at.insert(block, keep);
351 }
352 }
353 if at.is_empty() {
354 return;
355 }
356 for block in func.blocks().collect::<Vec<Block>>() {
357 let Some(terminator) = func.terminator(block) else {
358 continue;
359 };
360 for target in func.target_list(terminator).iter() {
361 let call = func[target];
362 let Some(keep) = at.get(&call.block) else {
363 continue;
364 };
365 let args: Vec<Value> = keep.iter().map(|&index| func[call.args][index]).collect();
366 let args = func.push_values(&args);
367 func.set_block_call(target, BlockCall { args, ..call });
368 }
369 }
370 for block in at.keys().copied().collect::<Vec<Block>>() {
371 func.retain_params(block, |param| !going.contains(¶m));
372 }
373}
374
375fn start_of_chain(func: &mut Func, first: Inst) -> Value {
381 let span = func.span(first);
382 let inst = func.create_inst(InstData::new(Opcode::MemEntry), &[Type::MEM], span);
383 func.insert_before(inst, first);
384 func[inst].results().next().expect("mem_entry produces one value")
385}
386
387fn thread(
394 func: &mut Func,
395 doms: &Dominators,
396 params: &HashMap<Block, Value>,
397 entry: Block,
398 start: Value,
399) -> HashMap<Block, Value> {
400 let mut forward: Vec<(Value, Value)> = Vec::new();
405 let mut ends = HashMap::new();
406 let mut stack = vec![(entry, start)];
407 while let Some((block, incoming)) = stack.pop() {
408 let mut current = params.get(&block).copied().unwrap_or(incoming);
409 for inst in func.insts(block).collect::<Vec<_>>() {
410 if !func[inst].opcode.touches_memory() {
411 continue;
412 }
413 let fresh = func.with_mem(inst, current);
414 func.insert_before(fresh, inst);
415 for (old, new) in func[inst].results().zip(func[fresh].results()) {
416 forward.push((old, new));
417 }
418 func.remove_inst(inst);
419 if let Some(next) = func.mem_out(fresh) {
420 current = next;
421 }
422 }
423 ends.insert(block, current);
424 stack.extend(doms.children(block).map(|child| (child, current)));
425 }
426
427 let forward: HashMap<Value, Value> = forward.into_iter().collect();
428 if !forward.is_empty() {
429 substitute(func, &forward);
430 }
431 ends
432}
433
434fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
436 let with = |value: Value| forward.get(&value).copied().unwrap_or(value);
437 for block in func.blocks().collect::<Vec<_>>() {
438 for inst in func.insts(block).collect::<Vec<_>>() {
439 let args = func[inst].args;
440 func.rewrite(args, with);
441 for call in func.successors(inst).collect::<Vec<_>>() {
442 func.rewrite(call.args, with);
443 }
444 }
445 }
446}
447
448fn pass_it_on(func: &mut Func, params: &HashMap<Block, Value>, ends: &HashMap<Block, Value>) {
450 for block in func.blocks().collect::<Vec<_>>() {
451 let Some(terminator) = func.terminator(block) else {
452 continue;
453 };
454 let Some(&value) = ends.get(&block) else {
455 continue;
456 };
457 for at in func.target_list(terminator).iter() {
458 let call = func[at];
459 if !params.contains_key(&call.block) {
460 continue;
461 }
462 let args = func.append_arg(call.args, value);
465 func.set_block_call(at, BlockCall { args, ..call });
466 }
467 }
468}
469
470fn iterated_frontier(cfg: &Cfg, doms: &Dominators, defs: &[Block]) -> HashSet<Block> {
473 let frontier = frontiers(cfg, doms);
474 let mut placed = HashSet::new();
475 let mut seen: HashSet<Block> = defs.iter().copied().collect();
476 let mut work: Vec<Block> = defs.to_vec();
477 while let Some(block) = work.pop() {
478 let Some(targets) = frontier.get(&block) else {
479 continue;
480 };
481 for &target in targets {
482 if placed.insert(target) && seen.insert(target) {
483 work.push(target);
484 }
485 }
486 }
487 placed
488}
489
490fn frontiers(cfg: &Cfg, doms: &Dominators) -> HashMap<Block, Vec<Block>> {
493 let mut frontier: HashMap<Block, Vec<Block>> = HashMap::new();
494 for block in cfg.reverse_postorder() {
495 let preds = cfg.predecessors(block);
496 if preds.len() < 2 {
497 continue;
498 }
499 let Some(top) = doms.immediate_dominator(block) else {
500 continue;
501 };
502 for &pred in preds {
503 let mut runner = pred;
504 while runner != top {
505 let at = frontier.entry(runner).or_default();
506 if !at.contains(&block) {
507 at.push(block);
508 }
509 let Some(next) = doms.immediate_dominator(runner) else {
510 break;
511 };
512 runner = next;
513 }
514 }
515 }
516 frontier
517}
518
519#[derive(Debug)]
524pub struct Walk<'a> {
525 func: &'a Func,
526 cfg: Cfg,
527 alias: Alias<'a>,
528 limit: u32,
529 counts: Counts,
530}
531
532impl<'a> Walk<'a> {
533 #[must_use]
535 pub fn new(func: &'a Func, outside: &'a Outside) -> Self {
536 Self::with(func, outside, Options::default(), MAX_ALIAS_QUERIES_PER_ACCESS)
537 }
538
539 #[must_use]
541 pub fn with(func: &'a Func, outside: &'a Outside, options: Options, limit: u32) -> Self {
542 Self {
543 func,
544 cfg: Cfg::new(func),
545 alias: Alias::with(func, outside, options),
546 limit,
547 counts: Counts::default(),
548 }
549 }
550
551 #[must_use]
553 pub const fn counts(&self) -> &Counts {
554 &self.counts
555 }
556
557 #[must_use]
561 pub fn knowing(mut self, summaries: &'a crate::modref::Summaries) -> Self {
562 self.alias = self.alias.knowing(summaries);
563 self
564 }
565
566 #[must_use]
568 pub const fn alias(&self) -> &Alias<'a> {
569 &self.alias
570 }
571
572 pub fn clobber(&mut self, load: Inst) -> Clobber {
578 self.clobber_with(load, &mut |_, _| Step::Stop)
579 }
580
581 pub fn clobber_with(
593 &mut self,
594 load: Inst,
595 translate: &mut dyn FnMut(&Access, Inst) -> Step,
596 ) -> Clobber {
597 let (Some(reference), Some(version)) = (self.alias.reads(load), self.func.mem_in(load))
598 else {
599 return Clobber::Unknown;
600 };
601 self.counts.walks += 1;
602 let mut budget = self.limit;
603 let mut seen = HashSet::new();
604 let answer = self.back(reference, version, &mut budget, &mut seen, translate);
605 answer.unwrap_or(Clobber::NoClobber)
608 }
609
610 fn back(
616 &mut self,
617 reference: Access,
618 version: Value,
619 budget: &mut u32,
620 seen: &mut HashSet<Value>,
621 translate: &mut dyn FnMut(&Access, Inst) -> Step,
622 ) -> Option<Clobber> {
623 if !seen.insert(version) {
624 return None;
625 }
626 match self.func[version].def {
627 Def::Param { block, index } => {
631 let mut answer = None;
632 for pred in self.cfg.predecessors(block).to_vec() {
633 let Some(terminator) = self.func.terminator(pred) else {
634 continue;
635 };
636 for call in self.func.successors(terminator).collect::<Vec<_>>() {
637 if call.block != block {
638 continue;
639 }
640 let Some(&incoming) = self.func[call.args].get(index as usize) else {
641 continue;
642 };
643 let one = self.back(reference, incoming, budget, seen, translate);
644 answer = combine(answer, one);
645 if answer == Some(Clobber::Unknown) {
646 return answer;
647 }
648 }
649 }
650 answer
651 }
652 Def::Result { inst, .. } => {
653 if self.func[inst].opcode == Opcode::MemEntry {
654 return Some(Clobber::NoClobber);
655 }
656 if *budget == 0 {
657 self.counts.exhausted += 1;
658 return Some(Clobber::Unknown);
659 }
660 *budget -= 1;
661 self.counts.steps += 1;
662 let past = match self.wrote(&reference, inst) {
663 None => reference,
664 Some(answer) => match translate(&reference, inst) {
665 Step::Stop => return Some(answer),
666 Step::Retry(next) => {
677 self.counts.rewritten += 1;
678 let before = self.func.mem_in(inst)?;
679 let mut fresh = HashSet::new();
680 return self.back(next, before, budget, &mut fresh, translate);
681 }
682 },
683 };
684 let next = self.func.mem_in(inst)?;
685 self.back(past, next, budget, seen, translate)
686 }
687 }
688 }
689
690 fn wrote(&mut self, reference: &Access, inst: Inst) -> Option<Clobber> {
695 if reference.volatile || self.func[inst].flags.contains(Flags::VOLATILE) {
699 return Some(Clobber::Maybe(inst));
700 }
701 if self.ordered(inst) {
705 return Some(Clobber::Maybe(inst));
706 }
707 if let Some(write) = self.alias.writes(inst) {
708 return match self.alias.query(reference, &write) {
709 Answer::No(_) => None,
710 Answer::May => Some(self.extent(reference, &write, inst)),
711 };
712 }
713 match self.alias.clobbered_by(reference, inst) {
717 Answer::No(_) => None,
718 Answer::May => Some(Clobber::Maybe(inst)),
719 }
720 }
721
722 fn extent(&self, reference: &Access, write: &Access, inst: Inst) -> Clobber {
735 if reference.origin != write.origin {
736 return Clobber::Maybe(inst);
737 }
738 let (Some(want), Some(wrote)) = (reference.range(), write.range()) else {
739 return Clobber::Maybe(inst);
740 };
741 if want == wrote {
742 Clobber::Exact(inst)
743 } else if wrote.0 < want.1 && want.0 < wrote.1 {
744 Clobber::Partial(inst)
745 } else {
746 Clobber::Maybe(inst)
749 }
750 }
751
752 fn ordered(&self, inst: Inst) -> bool {
754 use rucc_ir::Extra;
755 let order = match self.func[inst].extra {
756 Extra::Mem(at) => self.func[at].order,
757 Extra::Rmw(_, at) => self.func[at].order,
758 Extra::Order(order) => order,
759 _ => return false,
760 };
761 order != MemOrder::NotAtomic
762 }
763}
764
765fn combine(a: Option<Clobber>, b: Option<Clobber>) -> Option<Clobber> {
772 match (a, b) {
773 (None, other) | (other, None) => other,
774 (Some(one), Some(other)) if one == other => Some(one),
775 _ => Some(Clobber::Unknown),
776 }
777}
778
779#[cfg(test)]
780mod tests {
781 use rucc_base::Interner;
782 use rucc_ir::{Builder, MemInfo, Module, Restrict, Signature, parse, verify_func};
783
784 use super::*;
785
786 fn read(text: &str) -> (Module, Interner) {
788 let mut names = Interner::new();
789 let module = parse(text, &mut names).expect("the text parses");
790 (module, names)
791 }
792
793 const HEADER: &str = "\
794; ModuleID = 'mem.c'
795; format 0
796target triple = \"x86_64-unknown-linux-gnu\"
797target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
798";
799
800 fn wrap(signature: &str, body: &str) -> String {
801 format!("{HEADER}\nfunc @f{signature}, linkage(external) {{\n{body}}}\n")
802 }
803
804 fn built(text: &str) -> (Module, bool) {
808 let (mut module, names) = read(text);
809 let id = module.funcs().next().expect("one function");
810 let changed = build(&mut module[id]);
811 if let Err(errors) = verify_func(&module, &module[id], &names) {
812 panic!("{errors:#?}");
813 }
814 (module, changed)
815 }
816
817 fn one(module: &Module) -> &Func {
818 &module[module.funcs().next().expect("one function")]
819 }
820
821 fn nth(func: &Func, opcode: Opcode, want: usize) -> Inst {
823 func.blocks()
824 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
825 .filter(|&inst| func[inst].opcode == opcode)
826 .nth(want)
827 .expect("that many of them")
828 }
829
830 #[test]
831 fn a_function_with_no_memory_in_it_gets_no_chain() {
832 let text = wrap(
833 "(i32) -> i32",
834 "block0(%0: i32):
835 %1 = add %0, %0
836 return %1
837",
838 );
839 let (module, changed) = built(&text);
840 assert!(!changed);
841 assert_eq!(one(&module).blocks().count(), 1);
842 }
843
844 #[test]
845 fn a_straight_line_is_threaded_in_order() {
846 let text = wrap(
847 "(ptr) -> i32",
848 "block0(%0: ptr):
849 %1 = iconst.i32 7
850 store %1 -> %0, align 4
851 %2 = load.i32 %0, align 4
852 return %2
853",
854 );
855 let (module, changed) = built(&text);
856 assert!(changed);
857 let func = one(&module);
858 let start = nth(func, Opcode::MemEntry, 0);
859 let store = nth(func, Opcode::Store, 0);
860 let load = nth(func, Opcode::Load, 0);
861 assert_eq!(func.mem_in(store), func.mem_out(start));
862 assert_eq!(func.mem_in(load), func.mem_out(store));
863 assert_eq!(func.mem_out(load), None);
864 }
865
866 #[test]
867 fn a_join_gets_a_memory_parameter_and_every_branch_passes_one() {
868 let text = wrap(
869 "(ptr, i1) -> i32",
870 "block0(%0: ptr, %1: i1):
871 br_if %1, block1, block2
872
873block1:
874 %2 = iconst.i32 7
875 store %2 -> %0, align 4
876 jump block3
877
878block2:
879 jump block3
880
881block3:
882 %3 = load.i32 %0, align 4
883 return %3
884",
885 );
886 let (module, _) = built(&text);
887 let func = one(&module);
888 let join = func.blocks().nth(3).expect("four blocks");
889 assert_eq!(func[join].params.len(), 1);
890 let param = func[join].params[0];
891 assert!(func[param].ty.is_mem());
892 assert_eq!(func.mem_in(nth(func, Opcode::Load, 0)), Some(param));
893 }
894
895 #[test]
896 fn a_block_that_only_reads_needs_no_parameter() {
897 let text = wrap(
898 "(ptr, i1) -> i32",
899 "block0(%0: ptr, %1: i1):
900 br_if %1, block1, block2
901
902block1:
903 %2 = load.i32 %0, align 4
904 jump block3
905
906block2:
907 jump block3
908
909block3:
910 %3 = load.i32 %0, align 4
911 return %3
912",
913 );
914 let (module, _) = built(&text);
915 let func = one(&module);
916 for block in func.blocks() {
919 assert!(func[block].params.iter().all(|¶m| !func[param].ty.is_mem()));
920 }
921 }
922
923 #[test]
924 fn every_arm_of_a_switch_passes_its_own_version_along() {
925 let text = wrap(
926 "(ptr, i32) -> i32",
927 "block0(%0: ptr, %1: i32):
928 switch %1, block1, [0 => block2, 1 => block3]
929
930block1:
931 %2 = iconst.i32 1
932 store %2 -> %0, align 4
933 jump block4
934
935block2:
936 %3 = iconst.i32 2
937 store %3 -> %0, align 4
938 jump block4
939
940block3:
941 jump block4
942
943block4:
944 %4 = load.i32 %0, align 4
945 return %4
946",
947 );
948 let (module, _) = built(&text);
949 let func = one(&module);
950 let join = func.blocks().nth(4).expect("five blocks");
951 let param = *func[join].params.last().expect("a parameter");
952 assert!(func[param].ty.is_mem());
953 for (arm, want) in [(1, Some(0)), (2, Some(1)), (3, None)] {
956 let block = func.blocks().nth(arm).expect("that block");
957 let jump = func.terminator(block).expect("a terminator");
958 let call = func.successors(jump).next().expect("one target");
959 let sent = *func[call.args].last().expect("an argument");
960 let expect = match want {
961 Some(store) => func.mem_out(nth(func, Opcode::Store, store)),
962 None => func.mem_out(nth(func, Opcode::MemEntry, 0)),
963 };
964 assert_eq!(Some(sent), expect, "arm {arm} passed the wrong version");
965 }
966 }
967
968 #[test]
969 fn a_function_with_a_block_nothing_reaches_is_left_alone() {
970 let text = wrap(
971 "(ptr) -> i32",
972 "block0(%0: ptr):
973 %1 = iconst.i32 7
974 store %1 -> %0, align 4
975 jump block2
976
977block1:
978 %2 = iconst.i32 9
979 store %2 -> %0, align 4
980 jump block2
981
982block2:
983 %3 = load.i32 %0, align 4
984 return %3
985",
986 );
987 let (mut module, _) = read(&text);
990 let id = module.funcs().next().expect("one function");
991 assert!(!build(&mut module[id]));
992 assert_eq!(module[id].blocks().filter(|&b| !module[id][b].params.is_empty()).count(), 1);
993 }
994
995 fn last_load(func: &Func) -> Inst {
997 func.blocks()
998 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
999 .filter(|&inst| func[inst].opcode == Opcode::Load)
1000 .last()
1001 .expect("a load")
1002 }
1003
1004 fn walked(text: &str) -> (Clobber, Counts) {
1006 let (module, changed) = built(text);
1007 assert!(changed, "the function has memory in it");
1008 let func = one(&module);
1009 let outside = Outside::of(&module);
1010 let mut walk = Walk::new(func, &outside);
1011 let answer = walk.clobber(last_load(func));
1012 (answer, *walk.counts())
1013 }
1014
1015 #[test]
1016 fn a_load_sees_the_store_before_it() {
1017 let text = wrap(
1018 "(ptr) -> i32",
1019 "block0(%0: ptr):
1020 %1 = iconst.i32 7
1021 store %1 -> %0, align 4
1022 %2 = load.i32 %0, align 4
1023 return %2
1024",
1025 );
1026 let (answer, counts) = walked(&text);
1027 assert!(matches!(answer, Clobber::Exact(_)));
1028 assert_eq!(counts.walks(), 1);
1029 assert_eq!(counts.steps(), 1);
1030 assert_eq!(counts.exhausted(), 0);
1031 }
1032
1033 #[test]
1034 fn a_load_walks_past_a_store_to_another_object() {
1035 let text = wrap(
1036 "() -> i32",
1037 "block0:
1038 %0 = alloca, size 8, align 8
1039 %1 = alloca, size 8, align 8
1040 %2 = iconst.i32 7
1041 store %2 -> %0, align 4
1042 %3 = load.i32 %1, align 4
1043 return %3
1044",
1045 );
1046 let (answer, counts) = walked(&text);
1047 assert_eq!(answer, Clobber::NoClobber);
1048 assert_eq!(counts.steps(), 1);
1050 }
1051
1052 #[test]
1053 fn a_load_of_one_byte_of_a_wider_store_is_partial() {
1054 let text = wrap(
1055 "() -> i8",
1056 "block0:
1057 %0 = alloca, size 8, align 8
1058 %1 = iconst.i32 7
1059 store %1 -> %0, align 4
1060 %2 = iconst.i64 1
1061 %3 = ptr_add %0, %2
1062 %4 = load.i8 %3, align 1
1063 return %4
1064",
1065 );
1066 let (answer, _) = walked(&text);
1067 assert!(matches!(answer, Clobber::Partial(_)), "{answer:?}");
1068 }
1069
1070 #[test]
1071 fn a_load_after_a_call_that_cannot_reach_it_walks_past_the_call() {
1072 let text = wrap(
1073 "() -> i32",
1074 "block0:
1075 %0 = alloca, size 8, align 8
1076 %1 = iconst.i32 7
1077 store %1 -> %0, align 4
1078 call @g() : ()
1079 %2 = load.i32 %0, align 4
1080 return %2
1081",
1082 );
1083 let (answer, _) = walked(&text);
1086 assert!(matches!(answer, Clobber::Exact(_)), "{answer:?}");
1087 }
1088
1089 #[test]
1090 fn a_load_after_a_call_that_could_have_the_address_sees_the_call() {
1091 let text = wrap(
1092 "(ptr) -> i32",
1093 "block0(%0: ptr):
1094 %1 = iconst.i32 7
1095 store %1 -> %0, align 4
1096 call @g() : ()
1097 %2 = load.i32 %0, align 4
1098 return %2
1099",
1100 );
1101 let (answer, _) = walked(&text);
1102 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1103 }
1104
1105 #[test]
1106 fn a_load_after_an_atomic_store_sees_it_whatever_it_wrote() {
1107 let text = wrap(
1108 "() -> i32",
1109 "block0:
1110 %0 = alloca, size 8, align 8
1111 %1 = alloca, size 8, align 8
1112 %2 = iconst.i32 7
1113 atomic_store %2 -> %0, align 4, release
1114 %3 = load.i32 %1, align 4
1115 return %3
1116",
1117 );
1118 let (answer, _) = walked(&text);
1121 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1122 }
1123
1124 #[test]
1125 fn a_load_after_a_volatile_store_sees_it_whatever_it_wrote() {
1126 let text = wrap(
1127 "() -> i32",
1128 "block0:
1129 %0 = alloca, size 8, align 8
1130 %1 = alloca, size 8, align 8
1131 %2 = iconst.i32 7
1132 store.volatile %2 -> %0, align 4
1133 %3 = load.i32 %1, align 4
1134 return %3
1135",
1136 );
1137 let (answer, _) = walked(&text);
1138 assert!(matches!(answer, Clobber::Maybe(_)), "{answer:?}");
1139 }
1140
1141 #[test]
1142 fn paths_that_disagree_are_unknown_rather_than_the_weaker_of_the_two() {
1143 let text = wrap(
1144 "(i1) -> i32",
1145 "block0(%0: i1):
1146 %1 = alloca, size 8, align 8
1147 br_if %0, block1, block2
1148
1149block1:
1150 %2 = iconst.i32 7
1151 store %2 -> %1, align 4
1152 jump block3
1153
1154block2:
1155 jump block3
1156
1157block3:
1158 %3 = load.i32 %1, align 4
1159 return %3
1160",
1161 );
1162 let (answer, _) = walked(&text);
1163 assert_eq!(answer, Clobber::Unknown);
1164 }
1165
1166 #[test]
1167 fn a_loop_that_writes_nothing_relevant_walks_out_of_it() {
1168 let text = wrap(
1169 "(i32) -> i32",
1170 "block0(%0: i32):
1171 %1 = alloca, size 8, align 8
1172 %2 = alloca, size 8, align 8
1173 %3 = iconst.i32 7
1174 store %3 -> %1, align 4
1175 jump block1(%0)
1176
1177block1(%4: i32):
1178 %5 = iconst.i32 1
1179 %6 = sub %4, %5
1180 store %5 -> %2, align 4
1181 %7 = icmp sgt %6, %5
1182 br_if %7, block1(%6), block2
1183
1184block2:
1185 %8 = load.i32 %1, align 4
1186 return %8
1187",
1188 );
1189 let (answer, counts) = walked(&text);
1193 assert!(matches!(answer, Clobber::Exact(_)), "{answer:?}");
1194 assert_eq!(counts.exhausted(), 0);
1195 }
1196
1197 #[test]
1198 fn a_budget_of_nothing_gives_unknown_and_says_so() {
1199 let text = wrap(
1200 "(ptr) -> i32",
1201 "block0(%0: ptr):
1202 %1 = iconst.i32 7
1203 store %1 -> %0, align 4
1204 %2 = load.i32 %0, align 4
1205 return %2
1206",
1207 );
1208 let (module, _) = built(&text);
1209 let func = one(&module);
1210 let load = nth(func, Opcode::Load, 0);
1211 let outside = Outside::of(&module);
1212 let mut walk = Walk::with(func, &outside, Options::default(), 0);
1213 assert_eq!(walk.clobber(load), Clobber::Unknown);
1214 assert_eq!(walk.counts().exhausted(), 1);
1215 }
1216
1217 #[test]
1218 fn translate_carries_the_walk_past_a_def_it_would_have_stopped_at() {
1219 let text = wrap(
1220 "(ptr) -> i32",
1221 "block0(%0: ptr):
1222 %1 = iconst.i32 7
1223 store %1 -> %0, align 4
1224 memcpy %0, %0, size 4, align 4
1225 %2 = load.i32 %0, align 4
1226 return %2
1227",
1228 );
1229 let (module, _) = built(&text);
1230 let func = one(&module);
1231 let load = nth(func, Opcode::Load, 0);
1232
1233 let outside = Outside::of(&module);
1235 let mut walk = Walk::new(func, &outside);
1236 let stopped_at = walk.clobber(load).inst().expect("something wrote it");
1237 assert_eq!(func[stopped_at].opcode, Opcode::Memcpy);
1238
1239 let mut walk = Walk::new(func, &outside);
1242 let mut seen = Vec::new();
1243 let answer = walk.clobber_with(load, &mut |reference, inst| {
1244 seen.push(func[inst].opcode);
1245 if func[inst].opcode == Opcode::Memcpy { Step::Retry(*reference) } else { Step::Stop }
1246 });
1247 assert_eq!(seen, [Opcode::Memcpy, Opcode::Store]);
1248 assert_eq!(answer.inst().map(|inst| func[inst].opcode), Some(Opcode::Store));
1249
1250 assert_eq!(walk.counts().rewritten(), 1);
1253 }
1254
1255 #[test]
1256 fn building_twice_changes_nothing_the_second_time() {
1257 let text = wrap(
1258 "(ptr) -> i32",
1259 "block0(%0: ptr):
1260 %1 = load.i32 %0, align 4
1261 return %1
1262",
1263 );
1264 let (mut module, _) = read(&text);
1265 let id = module.funcs().next().expect("one function");
1266 let func = &mut module[id];
1267 assert!(build(func));
1268 let before = func.counts().insts;
1269 assert!(!build(func));
1270 assert_eq!(func.counts().insts, before);
1271 }
1272
1273 #[test]
1276 fn a_function_built_by_hand_threads_the_same_way() {
1277 let mut names = Interner::new();
1278 let i32_ = Type::int(32);
1279 let mut func = Func::new(
1280 names.intern("f"),
1281 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1282 );
1283 let entry = func.create_block();
1284 let addr = func.append_param(entry, Type::PTR);
1285 let info = MemInfo {
1286 size: 4,
1287 align: 4,
1288 order: MemOrder::NotAtomic,
1289 tbaa: None,
1290 owns: 0,
1291 restrict: Restrict::NONE,
1292 };
1293 let mut b = Builder::new(&mut func, entry);
1294 let seven = b.iconst(i32_, 7);
1295 b.store(seven, addr, info, Flags::NONE);
1296 let read = b.load(i32_, addr, info, Flags::NONE);
1297 b.ret(&[read]);
1298
1299 assert!(build(&mut func));
1300 let store = nth(&func, Opcode::Store, 0);
1301 let load = nth(&func, Opcode::Load, 0);
1302 assert_eq!(func.mem_in(load), func.mem_out(store));
1303 }
1304
1305 fn stripped(text: &str) -> (Module, bool) {
1309 let (mut module, names) = read(text);
1310 let id = module.funcs().next().expect("one function");
1311 build(&mut module[id]);
1312 if let Err(errors) = verify_func(&module, &module[id], &names) {
1313 panic!("after building: {errors:#?}");
1314 }
1315 let changed = strip(&mut module[id]);
1316 if let Err(errors) = verify_func(&module, &module[id], &names) {
1317 panic!("after stripping: {errors:#?}");
1318 }
1319 (module, changed)
1320 }
1321
1322 fn off(func: &Func) {
1324 for block in func.blocks() {
1325 assert!(
1326 func[block].params.iter().all(|¶m| !func[param].ty.is_mem()),
1327 "a block kept a memory parameter"
1328 );
1329 for inst in func.insts(block) {
1330 assert_ne!(
1331 func[inst].opcode,
1332 Opcode::MemEntry,
1333 "the start of the chain is still here"
1334 );
1335 assert!(!func.carries_mem(inst), "an instruction is still on the chain");
1336 }
1337 }
1338 }
1339
1340 #[test]
1341 fn a_straight_line_comes_off_the_chain_the_way_it_went_on() {
1342 let text = wrap(
1343 "(ptr) -> i32",
1344 "block0(%0: ptr):
1345 %1 = iconst.i32 7
1346 store %1 -> %0, align 4
1347 %2 = load.i32 %0, align 4
1348 return %2
1349",
1350 );
1351 let (module, changed) = stripped(&text);
1352 assert!(changed);
1353 let func = one(&module);
1354 off(func);
1355 let load = nth(func, Opcode::Load, 0);
1359 let param = func[func.entry().expect("an entry")].params[0];
1360 assert_eq!(func[func[load].args][0], param);
1361 let ret = nth(func, Opcode::Return, 0);
1362 assert_eq!(func[func[ret].args][0], func[load].results().next().expect("a result"));
1363 }
1364
1365 #[test]
1366 fn a_join_gives_its_memory_parameter_back_and_so_does_every_branch_to_it() {
1367 let text = wrap(
1368 "(ptr, i1) -> i32",
1369 "block0(%0: ptr, %1: i1):
1370 br_if %1, block1, block2
1371
1372block1:
1373 %2 = iconst.i32 7
1374 store %2 -> %0, align 4
1375 jump block3
1376
1377block2:
1378 jump block3
1379
1380block3:
1381 %3 = load.i32 %0, align 4
1382 return %3
1383",
1384 );
1385 let (module, changed) = stripped(&text);
1386 assert!(changed);
1387 let func = one(&module);
1388 off(func);
1389 let join = func.blocks().nth(3).expect("four blocks");
1390 assert!(func[join].params.is_empty(), "the join kept a parameter");
1391 for block in func.blocks() {
1392 let Some(terminator) = func.terminator(block) else { continue };
1393 for call in func.successors(terminator) {
1394 assert!(func[call.args].is_empty(), "a branch kept an argument");
1395 }
1396 }
1397 }
1398
1399 #[test]
1400 fn a_parameter_that_was_never_memory_keeps_its_place() {
1401 let text = wrap(
1404 "(ptr, i1) -> i32",
1405 "block0(%0: ptr, %1: i1):
1406 %2 = iconst.i32 7
1407 br_if %1, block1(%2), block2
1408
1409block1(%3: i32):
1410 store %3 -> %0, align 4
1411 jump block3
1412
1413block2:
1414 jump block3
1415
1416block3:
1417 %4 = load.i32 %0, align 4
1418 return %4
1419",
1420 );
1421 let (module, _) = stripped(&text);
1422 let func = one(&module);
1423 off(func);
1424 let arm = func.blocks().nth(1).expect("four blocks");
1425 assert_eq!(func[arm].params.len(), 1);
1426 let param = func[arm].params[0];
1427 assert_eq!(func[param].ty, Type::int(32));
1428 let store = nth(func, Opcode::Store, 0);
1429 assert_eq!(func[func[store].args][0], param, "the store lost the value it writes");
1430 }
1431
1432 #[test]
1433 fn a_function_that_was_never_on_the_chain_is_left_alone() {
1434 let text = wrap(
1435 "(i32) -> i32",
1436 "block0(%0: i32):
1437 %1 = add %0, %0
1438 return %1
1439",
1440 );
1441 let (mut module, names) = read(&text);
1442 let id = module.funcs().next().expect("one function");
1443 assert!(!strip(&mut module[id]));
1444 if let Err(errors) = verify_func(&module, &module[id], &names) {
1445 panic!("{errors:#?}");
1446 }
1447 }
1448
1449 #[test]
1450 fn a_call_that_returns_something_keeps_it() {
1451 let text = format!(
1454 "{HEADER}\nfunc @f() -> i32, linkage(external) {{\nblock0:\n %0 = call @g() : () -> \
1455 i32\n return %0\n}}\n"
1456 );
1457 let (module, changed) = stripped(&text);
1458 assert!(changed);
1459 let func = one(&module);
1460 off(func);
1461 let call = nth(func, Opcode::Call, 0);
1462 let ret = nth(func, Opcode::Return, 0);
1463 assert_eq!(func[call].results().count(), 1);
1464 assert_eq!(func[func[ret].args][0], func[call].results().next().expect("a result"));
1465 }
1466}