1use std::collections::{HashMap, HashSet};
57
58use rucc_base::{Interner, Symbol};
59use rucc_ir::{AttrSet, Extra, Flags, Func, Inst, InstData, MemOrder, Module, Opcode, Value};
60
61use crate::alias::{Escapes, Origin, origin};
62use crate::callgraph::{CallGraph, Node};
63use crate::cfg::Cfg;
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub enum Purity {
72 Const,
74 LoopingConst,
77 Pure,
79 LoopingPure,
81 Opaque,
83}
84
85impl Purity {
86 pub const ALL: [Self; 5] =
88 [Self::Const, Self::LoopingConst, Self::Pure, Self::LoopingPure, Self::Opaque];
89
90 #[must_use]
92 pub const fn as_str(self) -> &'static str {
93 match self {
94 Self::Const => "const",
95 Self::LoopingConst => "const, may not return",
96 Self::Pure => "pure",
97 Self::LoopingPure => "pure, may not return",
98 Self::Opaque => "opaque",
99 }
100 }
101
102 #[must_use]
104 pub const fn reads_memory(self) -> bool {
105 match self {
106 Self::Const | Self::LoopingConst => false,
107 Self::Pure | Self::LoopingPure | Self::Opaque => true,
108 }
109 }
110
111 #[must_use]
116 pub const fn writes_memory(self) -> bool {
117 matches!(self, Self::Opaque)
118 }
119
120 #[must_use]
126 pub const fn terminates(self) -> bool {
127 matches!(self, Self::Const | Self::Pure)
128 }
129
130 #[must_use]
137 pub const fn depends_only_on_arguments(self) -> bool {
138 !self.reads_memory() && !self.writes_memory()
139 }
140
141 #[must_use]
147 pub const fn can_be_deleted_when_unused(self) -> bool {
148 !self.writes_memory() && self.terminates()
149 }
150
151 #[must_use]
157 pub const fn stronger(self, other: Self) -> Self {
158 match (self, other) {
159 (Self::Opaque, it) | (it, Self::Opaque) => it,
160 (one, two) => Self::of(
161 one.reads_memory() && two.reads_memory(),
162 one.terminates() || two.terminates(),
163 ),
164 }
165 }
166
167 #[must_use]
172 pub const fn weaker(self, other: Self) -> Self {
173 match (self, other) {
174 (Self::Opaque, _) | (_, Self::Opaque) => Self::Opaque,
175 (one, two) => Self::of(
176 one.reads_memory() || two.reads_memory(),
177 one.terminates() && two.terminates(),
178 ),
179 }
180 }
181
182 const fn of(reads: bool, terminates: bool) -> Self {
184 match (reads, terminates) {
185 (false, true) => Self::Const,
186 (false, false) => Self::LoopingConst,
187 (true, true) => Self::Pure,
188 (true, false) => Self::LoopingPure,
189 }
190 }
191}
192
193impl std::fmt::Display for Purity {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 f.write_str(self.as_str())
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
204pub enum Callee {
205 Direct(Symbol),
207 Indirect,
209 Intrinsic(Symbol),
212 Asm,
214}
215
216impl Callee {
217 #[must_use]
219 pub fn of(func: &Func, inst: Inst) -> Option<Self> {
220 let data = &func[inst];
221 match data.opcode {
222 Opcode::Call | Opcode::TailCall | Opcode::CallIndirect => match data.extra {
223 Extra::Call(at) => Some(match func[at].callee {
224 Some(name) => Self::Direct(name),
225 None => Self::Indirect,
226 }),
227 _ => Some(Self::Indirect),
228 },
229 Opcode::TargetIntrinsic => match data.extra {
230 Extra::Symbol(name) => Some(Self::Intrinsic(name)),
231 _ => Some(Self::Asm),
232 },
233 Opcode::InlineAsm => Some(Self::Asm),
234 Opcode::Apply => Some(Self::Indirect),
236 _ => None,
237 }
238 }
239}
240
241#[derive(Debug, Clone, Default)]
247pub struct Facts {
248 declared: HashMap<Symbol, AttrSet>,
249 inferred: HashMap<Symbol, Purity>,
250 from_the_library: HashMap<Symbol, Purity>,
251}
252
253impl Facts {
254 #[must_use]
256 pub fn nothing() -> Self {
257 Self::default()
258 }
259
260 #[must_use]
265 pub fn of_module(module: &Module, names: &Interner) -> Self {
266 let mut facts = Self::default();
267 let mut defined = HashSet::new();
268 for id in module.funcs() {
269 let func = &module[id];
270 facts.declared.insert(func.name, func.attrs.set);
271 if !func.is_declaration() {
272 defined.insert(func.name);
273 }
274 }
275 for &name in facts.declared.keys() {
278 if defined.contains(&name) {
279 continue;
280 }
281 if let Some(purity) = library_purity(names.resolve(name)) {
282 facts.from_the_library.insert(name, purity);
283 }
284 }
285 facts
286 }
287
288 pub fn without_the_library(&mut self) {
293 self.from_the_library.clear();
294 }
295
296 pub fn not_the_library_name(&mut self, name: Symbol) {
301 self.from_the_library.remove(&name);
302 }
303
304 pub fn record_inferred(&mut self, name: Symbol, purity: Purity) {
310 self.inferred.insert(name, purity);
311 }
312
313 #[must_use]
315 pub fn declared(&self, name: Symbol) -> Purity {
316 match self.declared.get(&name) {
317 Some(&set) => from_attributes(set),
318 None => Purity::Opaque,
319 }
320 }
321
322 #[must_use]
324 pub fn inferred(&self, name: Symbol) -> Purity {
325 self.inferred.get(&name).copied().unwrap_or(Purity::Opaque)
326 }
327
328 #[must_use]
332 pub fn purity_of(&self, callee: Callee) -> Purity {
333 match callee {
334 Callee::Direct(name) => self.of_name(name),
335 Callee::Indirect => Purity::Opaque,
338 Callee::Intrinsic(_) => Purity::Opaque,
341 Callee::Asm => Purity::Opaque,
343 }
344 }
345
346 fn of_name(&self, name: Symbol) -> Purity {
348 self.what_was_said_about(name).stronger(self.inferred(name))
349 }
350
351 fn what_was_said_about(&self, name: Symbol) -> Purity {
357 match self.from_the_library.get(&name) {
358 Some(&known) => self.declared(name).stronger(known),
359 None => self.declared(name),
360 }
361 }
362}
363
364pub fn infer(module: &Module, graph: &CallGraph, facts: &mut Facts) {
380 let answers = graph.solve(
384 |_| Purity::Const,
385 |node, answers| match graph.trusted_body(node) {
386 Some(id) if !graph.reaches_unknown(node) => {
391 let purity = what_the_body_does(&module[id], graph, answers, facts);
392 match in_a_cycle(graph, node) {
397 true => purity.weaker(Purity::LoopingConst),
398 false => purity,
399 }
400 }
401 _ => Purity::Opaque,
404 },
405 );
406 for node in graph.nodes() {
407 let purity = answers[node.index()];
408 if purity != Purity::Opaque {
409 facts.record_inferred(graph.name(node), purity);
410 }
411 }
412}
413
414fn what_the_body_does(func: &Func, graph: &CallGraph, answers: &[Purity], facts: &Facts) -> Purity {
420 let mut so_far = Purity::Const;
421 let mut escapes: Option<Escapes> = None;
424 for block in func.blocks() {
425 for inst in func.insts(block) {
426 let data = func[inst];
427 so_far = so_far.weaker(if let Some(callee) = Callee::of(func, inst) {
428 what_that_call_does(callee, graph, answers, facts)
429 } else if !data.opcode.has_effects() || data.opcode.is_terminator() {
430 Purity::Const
433 } else {
434 match data.opcode {
435 Opcode::Alloca => Purity::Const,
438 Opcode::Load if plain(func, data) => {
439 let escapes = escapes.get_or_insert_with(|| Escapes::of(func));
440 match ours(func, escapes, func[data.args][0]) {
441 true => Purity::Const,
442 false => Purity::Pure,
443 }
444 }
445 Opcode::Store if plain(func, data) => {
446 let escapes = escapes.get_or_insert_with(|| Escapes::of(func));
447 match ours(func, escapes, func[data.args][1]) {
448 true => Purity::Const,
449 false => Purity::Opaque,
450 }
451 }
452 _ => Purity::Opaque,
457 }
458 });
459 if so_far == Purity::Opaque {
460 return Purity::Opaque;
461 }
462 }
463 }
464 match has_a_cycle(func) {
466 true => so_far.weaker(Purity::LoopingConst),
467 false => so_far,
468 }
469}
470
471fn what_that_call_does(
478 callee: Callee,
479 graph: &CallGraph,
480 answers: &[Purity],
481 facts: &Facts,
482) -> Purity {
483 let Callee::Direct(name) = callee else {
484 return Purity::Opaque;
487 };
488 let said = facts.what_was_said_about(name);
489 match graph.node(name) {
490 Some(node) => said.stronger(answers[node.index()]),
491 None => said,
495 }
496}
497
498fn plain(func: &Func, data: InstData) -> bool {
504 if data.flags.contains(Flags::VOLATILE) {
505 return false;
506 }
507 match data.extra {
508 Extra::Mem(mem) => func[mem].order == MemOrder::NotAtomic,
509 _ => false,
510 }
511}
512
513fn ours(func: &Func, escapes: &Escapes, pointer: Value) -> bool {
520 matches!(origin(func, pointer).0, Origin::Local(local) if !escapes.escaped(local))
521}
522
523fn in_a_cycle(graph: &CallGraph, node: Node) -> bool {
529 graph.components()[graph.component_of(node)].len() > 1 || graph.calls(node).contains(&node)
530}
531
532fn has_a_cycle(func: &Func) -> bool {
543 let cfg = Cfg::new(func);
544 func.blocks().any(|block| {
545 let Some(from) = cfg.rank(block) else { return false };
546 cfg.successors(block).iter().any(|&to| cfg.rank(to).is_some_and(|to| to <= from))
547 })
548}
549
550fn from_attributes(set: AttrSet) -> Purity {
556 let terminates = !set.contains(AttrSet::NORETURN);
557 if set.contains(AttrSet::READNONE) {
558 return Purity::of(false, terminates);
559 }
560 if set.contains(AttrSet::READONLY) {
561 return Purity::of(true, terminates);
562 }
563 Purity::Opaque
564}
565
566const LIBRARY: &[(&str, Purity)] = &[
575 ("abs", Purity::Const),
576 ("imaxabs", Purity::Const),
577 ("labs", Purity::Const),
578 ("llabs", Purity::Const),
579 ("memchr", Purity::Pure),
580 ("memcmp", Purity::Pure),
581 ("strchr", Purity::Pure),
582 ("strcmp", Purity::Pure),
583 ("strcspn", Purity::Pure),
584 ("strlen", Purity::Pure),
585 ("strncmp", Purity::Pure),
586 ("strnlen", Purity::Pure),
587 ("strpbrk", Purity::Pure),
588 ("strrchr", Purity::Pure),
589 ("strspn", Purity::Pure),
590 ("strstr", Purity::Pure),
591];
592
593fn library_purity(name: &str) -> Option<Purity> {
598 let name = name.strip_prefix("__builtin_").unwrap_or(name);
599 LIBRARY.binary_search_by_key(&name, |&(named, _)| named).ok().map(|at| LIBRARY[at].1)
600}
601
602#[cfg(test)]
603mod tests {
604 use rucc_base::Interner;
605 use rucc_ir::{
606 AsmInfo, AttrSet, BlockCallList, Builder, CallInfo, Extra, Flags, Func, InstData, IntPred,
607 MemInfo, MemOrder, Module, Opcode, Pic, Restrict, Signature, Type, Value,
608 };
609 use rucc_target::{TargetInfo, Triple};
610
611 use super::{CallGraph, Callee, Facts, LIBRARY, Purity, infer};
612
613 fn module(named: &[(&str, bool, AttrSet)]) -> (Interner, Module) {
615 let mut names = Interner::new();
616 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
617 let mut module = Module::new(names.intern("t.c"), &target);
618 for &(name, defined, attrs) in named {
619 let mut func = Func::new(names.intern(name), Signature::new());
620 func.attrs.set = attrs;
621 if defined {
622 let block = func.create_block();
623 let mut build = Builder::new(&mut func, block);
624 let zero = build.iconst(Type::int(32), 0);
625 build.ret(&[zero]);
626 }
627 module.add_func(func);
628 }
629 (names, module)
630 }
631
632 fn purity(names: &mut Interner, module: &Module, name: &str) -> Purity {
634 let facts = Facts::of_module(module, names);
635 let symbol = names.intern(name);
636 facts.purity_of(Callee::Direct(symbol))
637 }
638
639 #[test]
640 fn a_function_nobody_promised_anything_about_is_opaque() {
641 let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
642 assert_eq!(purity(&mut names, &module, "f"), Purity::Opaque);
643 }
644
645 #[test]
646 fn a_name_this_module_never_heard_of_is_opaque_as_well() {
647 let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
648 let facts = Facts::of_module(&module, &names);
649 assert_eq!(facts.purity_of(Callee::Direct(names.intern("g"))), Purity::Opaque);
650 }
651
652 #[test]
653 fn the_const_attribute_is_honoured_because_the_user_asserted_it() {
654 let (mut names, module) = module(&[("f", false, AttrSet::READNONE)]);
655 let purity = purity(&mut names, &module, "f");
656 assert_eq!(purity, Purity::Const);
657 assert!(purity.depends_only_on_arguments());
658 assert!(purity.can_be_deleted_when_unused());
659 }
660
661 #[test]
662 fn the_pure_attribute_reads_memory_and_writes_none() {
663 let (mut names, module) = module(&[("f", false, AttrSet::READONLY)]);
664 let purity = purity(&mut names, &module, "f");
665 assert_eq!(purity, Purity::Pure);
666 assert!(purity.reads_memory());
667 assert!(!purity.writes_memory());
668 assert!(!purity.depends_only_on_arguments());
669 assert!(purity.can_be_deleted_when_unused());
670 }
671
672 #[test]
673 fn a_const_function_that_does_not_come_back_may_not_be_deleted() {
674 let (mut names, module) =
677 module(&[("f", false, AttrSet::READNONE.union(AttrSet::NORETURN))]);
678 let purity = purity(&mut names, &module, "f");
679 assert_eq!(purity, Purity::LoopingConst);
680 assert!(purity.depends_only_on_arguments());
681 assert!(!purity.can_be_deleted_when_unused());
682 }
683
684 #[test]
685 fn nothing_that_is_not_a_direct_call_is_anything_but_opaque() {
686 let (mut names, module) = module(&[("f", true, AttrSet::READNONE)]);
687 let facts = Facts::of_module(&module, &names);
688 assert_eq!(facts.purity_of(Callee::Indirect), Purity::Opaque);
691 assert_eq!(facts.purity_of(Callee::Asm), Purity::Opaque);
692 let vector = names.intern("__builtin_ia32_paddb");
693 assert_eq!(facts.purity_of(Callee::Intrinsic(vector)), Purity::Opaque);
694 }
695
696 #[test]
697 fn the_library_names_are_known_under_both_spellings() {
698 let (mut names, module) = module(&[
699 ("strlen", false, AttrSet::NONE),
700 ("abs", false, AttrSet::NONE),
701 ("__builtin_strlen", false, AttrSet::NONE),
702 ("printf", false, AttrSet::NONE),
703 ]);
704 assert_eq!(purity(&mut names, &module, "strlen"), Purity::Pure);
705 assert_eq!(purity(&mut names, &module, "__builtin_strlen"), Purity::Pure);
706 assert_eq!(purity(&mut names, &module, "abs"), Purity::Const);
707 assert_eq!(purity(&mut names, &module, "printf"), Purity::Opaque);
709 }
710
711 #[test]
712 fn a_module_that_defines_strlen_means_its_own() {
713 let (mut names, module) = module(&[("strlen", true, AttrSet::NONE)]);
714 assert_eq!(purity(&mut names, &module, "strlen"), Purity::Opaque);
715 }
716
717 #[test]
718 fn no_builtin_takes_the_table_away_and_the_named_form_takes_one_entry() {
719 let (mut names, module) =
720 module(&[("strlen", false, AttrSet::NONE), ("abs", false, AttrSet::NONE)]);
721 let mut facts = Facts::of_module(&module, &names);
722 let strlen = names.intern("strlen");
723 let abs = names.intern("abs");
724 facts.not_the_library_name(strlen);
725 assert_eq!(facts.purity_of(Callee::Direct(strlen)), Purity::Opaque);
726 assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Const);
727 facts.without_the_library();
728 assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Opaque);
729 }
730
731 #[test]
732 fn what_the_user_wrote_and_what_analysis_worked_out_are_kept_apart() {
733 let (mut names, module) =
734 module(&[("f", true, AttrSet::READNONE.union(AttrSet::NORETURN))]);
735 let mut facts = Facts::of_module(&module, &names);
736 let f = names.intern("f");
737 assert_eq!(facts.declared(f), Purity::LoopingConst);
738 assert_eq!(facts.inferred(f), Purity::Opaque);
739 facts.record_inferred(f, Purity::Pure);
743 assert_eq!(facts.declared(f), Purity::LoopingConst);
744 assert_eq!(facts.inferred(f), Purity::Pure);
745 assert_eq!(facts.purity_of(Callee::Direct(f)), Purity::Const);
746 }
747
748 #[test]
749 fn what_an_instruction_calls_is_read_off_the_instruction() {
750 let mut names = Interner::new();
751 let mut func = Func::new(names.intern("caller"), Signature::new());
752 let block = func.create_block();
753 let mut build = Builder::new(&mut func, block);
754 let signature = build.func().add_signature(Signature::new());
755 let direct = build.call(names.intern("f"), signature, &[]);
756 let varargs = build.func().push_abis(&[]);
757 let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
758 let indirect = build.inst(
759 InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
760 &[],
761 );
762 let asm = build.inline_asm(
763 AsmInfo {
764 template: names.intern("nop"),
765 constraints: names.intern(""),
766 clobbers: names.intern(""),
767 targets: BlockCallList::EMPTY,
768 },
769 &[],
770 &[],
771 Flags::NONE,
772 );
773 let nothing = build.ret(&[]);
774
775 let f = names.intern("f");
776 assert_eq!(Callee::of(&func, nothing), None);
777 assert_eq!(Callee::of(&func, direct), Some(Callee::Direct(f)));
778 assert_eq!(Callee::of(&func, indirect), Some(Callee::Indirect));
779 assert_eq!(Callee::of(&func, asm), Some(Callee::Asm));
780 }
781
782 #[test]
783 fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
784 for one in Purity::ALL {
785 assert_eq!(one.stronger(one), one, "{one} is not idempotent");
786 assert_eq!(one.weaker(one), one, "{one} is not idempotent");
787 assert_eq!(one.stronger(Purity::Opaque), one, "opaque should say nothing");
788 assert_eq!(one.weaker(Purity::Opaque), Purity::Opaque, "opaque covers everything");
789 for two in Purity::ALL {
790 assert_eq!(one.stronger(two), two.stronger(one), "{one} and {two} disagree");
791 assert_eq!(one.weaker(two), two.weaker(one), "{one} and {two} disagree");
792 let both = one.weaker(two);
794 assert!(both.reads_memory() >= one.reads_memory());
795 assert!(both.writes_memory() >= one.writes_memory());
796 assert!(both.terminates() <= one.terminates());
797 }
798 }
799 }
800
801 #[test]
802 fn only_an_opaque_call_may_write_memory() {
803 for purity in Purity::ALL {
804 assert_eq!(purity.writes_memory(), purity == Purity::Opaque, "{purity}");
805 assert_eq!(purity.can_be_deleted_when_unused(), purity.terminates(), "{purity}");
806 }
807 }
808
809 #[test]
810 fn the_library_table_is_sorted_says_each_name_once_and_writes_no_memory() {
811 for pair in LIBRARY.windows(2) {
814 assert!(pair[0].0 < pair[1].0, "{} and {} are out of order", pair[0].0, pair[1].0);
815 }
816 for &(name, purity) in LIBRARY {
817 assert!(!purity.writes_memory(), "{name} would not be worth an entry");
818 assert!(purity.terminates(), "{name} is in the table to be deletable");
819 assert!(!name.starts_with("__builtin_"), "{name} is reached under both spellings");
820 }
821 }
822
823 fn access() -> MemInfo {
825 MemInfo {
826 size: 4,
827 align: 4,
828 owns: 4,
829 order: MemOrder::NotAtomic,
830 tbaa: None,
831 restrict: Restrict::NONE,
832 }
833 }
834
835 fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
837 let name = names.intern("v");
838 build.value(
839 InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
840 Type::PTR,
841 )
842 }
843
844 fn stack(build: &mut Builder<'_>) -> Value {
846 let mem = build.func().add_mem(access());
847 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
848 }
849
850 fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str) {
852 let name = names.intern(name);
853 let signature = build.func().add_signature(Signature::new());
854 build.call(name, signature, &[]);
855 }
856
857 type Body = fn(&mut Interner, &mut Func);
859
860 struct Worked {
865 names: Interner,
866 facts: Facts,
867 }
868
869 impl Worked {
870 fn out(bodies: &[(&str, Body)]) -> Self {
872 Self::linked(Pic::Executable, bodies)
873 }
874
875 fn linked(pic: Pic, bodies: &[(&str, Body)]) -> Self {
877 let mut names = Interner::new();
878 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
879 let mut module = Module::new(names.intern("t.c"), &target);
880 for &(name, body) in bodies {
881 let mut func = Func::new(names.intern(name), Signature::new());
882 body(&mut names, &mut func);
883 module.add_func(func);
884 }
885 let mut facts = Facts::of_module(&module, &names);
886 infer(&module, &CallGraph::of(&module, pic), &mut facts);
887 Self { names, facts }
888 }
889
890 fn about(&mut self, name: &str) -> Purity {
892 let name = self.names.intern(name);
893 self.facts.inferred(name)
894 }
895
896 fn at_a_call_site(&mut self, name: &str) -> Purity {
898 let name = self.names.intern(name);
899 self.facts.purity_of(Callee::Direct(name))
900 }
901 }
902
903 fn only_arithmetic(_: &mut Interner, func: &mut Func) {
905 let block = func.create_block();
906 let mut build = Builder::new(func, block);
907 let a = build.iconst(Type::int(32), 2);
908 let b = build.iconst(Type::int(32), 3);
909 let sum = build.binary(Opcode::Add, a, b, Flags::NONE);
910 build.ret(&[sum]);
911 }
912
913 fn empty(_: &mut Interner, func: &mut Func) {
915 let block = func.create_block();
916 Builder::new(func, block).ret(&[]);
917 }
918
919 fn declared(_: &mut Interner, _: &mut Func) {}
921
922 #[test]
923 fn a_function_that_only_computes_is_const() {
924 assert_eq!(Worked::out(&[("f", only_arithmetic)]).about("f"), Purity::Const);
925 }
926
927 #[test]
928 fn a_function_that_reads_memory_it_did_not_make_is_pure() {
929 fn reads(names: &mut Interner, func: &mut Func) {
930 let block = func.create_block();
931 let mut build = Builder::new(func, block);
932 let at = somewhere(&mut build, names);
933 let value = build.load(Type::int(32), at, access(), Flags::NONE);
934 build.ret(&[value]);
935 }
936 let mut worked = Worked::out(&[("f", reads)]);
937 assert_eq!(worked.about("f"), Purity::Pure);
938 assert!(worked.about("f").can_be_deleted_when_unused());
939 }
940
941 #[test]
942 fn a_function_that_writes_memory_it_did_not_make_is_opaque() {
943 fn writes(names: &mut Interner, func: &mut Func) {
944 let block = func.create_block();
945 let mut build = Builder::new(func, block);
946 let at = somewhere(&mut build, names);
947 let zero = build.iconst(Type::int(32), 0);
948 build.store(zero, at, access(), Flags::NONE);
949 build.ret(&[]);
950 }
951 assert_eq!(Worked::out(&[("f", writes)]).about("f"), Purity::Opaque);
952 }
953
954 #[test]
955 fn a_local_this_function_kept_to_itself_is_not_memory() {
956 fn temporary(_: &mut Interner, func: &mut Func) {
960 let block = func.create_block();
961 let mut build = Builder::new(func, block);
962 let at = stack(&mut build);
963 let zero = build.iconst(Type::int(32), 0);
964 build.store(zero, at, access(), Flags::NONE);
965 let back = build.load(Type::int(32), at, access(), Flags::NONE);
966 build.ret(&[back]);
967 }
968 assert_eq!(Worked::out(&[("f", temporary)]).about("f"), Purity::Const);
969 }
970
971 #[test]
972 fn a_local_whose_address_the_function_hands_back_is_memory_like_any_other() {
973 fn handed_back(_: &mut Interner, func: &mut Func) {
974 let block = func.create_block();
975 let mut build = Builder::new(func, block);
976 let at = stack(&mut build);
977 let zero = build.iconst(Type::int(32), 0);
978 build.store(zero, at, access(), Flags::NONE);
979 build.ret(&[at]);
980 }
981 assert_eq!(Worked::out(&[("f", handed_back)]).about("f"), Purity::Opaque);
982 }
983
984 #[test]
985 fn a_volatile_read_is_opaque_however_private_the_storage_is() {
986 fn volatile(_: &mut Interner, func: &mut Func) {
989 let block = func.create_block();
990 let mut build = Builder::new(func, block);
991 let at = stack(&mut build);
992 let value = build.load(Type::int(32), at, access(), Flags::VOLATILE);
993 build.ret(&[value]);
994 }
995 assert_eq!(Worked::out(&[("f", volatile)]).about("f"), Purity::Opaque);
996 }
997
998 #[test]
999 fn a_caller_is_what_the_function_it_calls_is() {
1000 fn calls_g(names: &mut Interner, func: &mut Func) {
1001 let block = func.create_block();
1002 let mut build = Builder::new(func, block);
1003 calls(&mut build, names, "g");
1004 build.ret(&[]);
1005 }
1006 let mut worked = Worked::out(&[("f", calls_g), ("g", only_arithmetic)]);
1007 assert_eq!(worked.about("g"), Purity::Const);
1008 assert_eq!(worked.about("f"), Purity::Const);
1009 }
1010
1011 #[test]
1012 fn a_caller_of_something_nobody_can_see_is_opaque() {
1013 fn calls_g(names: &mut Interner, func: &mut Func) {
1014 let block = func.create_block();
1015 let mut build = Builder::new(func, block);
1016 calls(&mut build, names, "g");
1017 build.ret(&[]);
1018 }
1019 let mut worked = Worked::out(&[("f", calls_g), ("g", declared)]);
1020 assert_eq!(worked.about("g"), Purity::Opaque);
1021 assert_eq!(worked.about("f"), Purity::Opaque);
1022 }
1023
1024 #[test]
1025 fn what_the_library_says_about_a_callee_reaches_the_caller() {
1026 fn calls_strlen(names: &mut Interner, func: &mut Func) {
1027 let block = func.create_block();
1028 let mut build = Builder::new(func, block);
1029 calls(&mut build, names, "strlen");
1030 build.ret(&[]);
1031 }
1032 let mut worked = Worked::out(&[("f", calls_strlen), ("strlen", declared)]);
1033 assert_eq!(worked.about("f"), Purity::Pure);
1034 }
1035
1036 #[test]
1037 fn two_functions_that_call_each_other_and_do_nothing_else_are_not_opaque() {
1038 fn calls_g(names: &mut Interner, func: &mut Func) {
1043 let block = func.create_block();
1044 let mut build = Builder::new(func, block);
1045 calls(&mut build, names, "g");
1046 build.ret(&[]);
1047 }
1048 fn calls_f(names: &mut Interner, func: &mut Func) {
1049 let block = func.create_block();
1050 let mut build = Builder::new(func, block);
1051 calls(&mut build, names, "f");
1052 build.ret(&[]);
1053 }
1054 let mut worked = Worked::out(&[("f", calls_g), ("g", calls_f)]);
1055 assert_eq!(worked.about("f"), Purity::LoopingConst);
1056 assert_eq!(worked.about("g"), Purity::LoopingConst);
1057 assert!(worked.about("f").depends_only_on_arguments());
1058 assert!(!worked.about("f").can_be_deleted_when_unused());
1059 }
1060
1061 #[test]
1062 fn a_function_that_calls_itself_may_not_come_back() {
1063 fn calls_itself(names: &mut Interner, func: &mut Func) {
1064 let block = func.create_block();
1065 let mut build = Builder::new(func, block);
1066 calls(&mut build, names, "f");
1067 build.ret(&[]);
1068 }
1069 assert_eq!(Worked::out(&[("f", calls_itself)]).about("f"), Purity::LoopingConst);
1070 }
1071
1072 #[test]
1073 fn a_function_with_a_loop_in_it_may_not_come_back() {
1074 fn loops(_: &mut Interner, func: &mut Func) {
1077 let entry = func.create_block();
1078 let head = func.create_block();
1079 let done = func.create_block();
1080 let mut build = Builder::new(func, entry);
1081 build.jump(head, &[]);
1082 let mut build = Builder::new(func, head);
1083 let zero = build.iconst(Type::int(32), 0);
1084 let cond = build.icmp(IntPred::Eq, zero, zero);
1085 build.br_if(cond, head, &[], done, &[]);
1086 Builder::new(func, done).ret(&[]);
1087 }
1088 let mut worked = Worked::out(&[("f", loops)]);
1089 assert_eq!(worked.about("f"), Purity::LoopingConst);
1090 assert!(!worked.about("f").can_be_deleted_when_unused());
1091 }
1092
1093 #[test]
1094 fn a_branch_that_joins_again_is_not_a_loop() {
1095 fn branches(_: &mut Interner, func: &mut Func) {
1098 let entry = func.create_block();
1099 let arm = func.create_block();
1100 let join = func.create_block();
1101 let mut build = Builder::new(func, entry);
1102 let zero = build.iconst(Type::int(32), 0);
1103 let cond = build.icmp(IntPred::Eq, zero, zero);
1104 build.br_if(cond, arm, &[], join, &[]);
1105 Builder::new(func, arm).jump(join, &[]);
1106 Builder::new(func, join).ret(&[]);
1107 }
1108 assert_eq!(Worked::out(&[("f", branches)]).about("f"), Purity::Const);
1109 }
1110
1111 #[test]
1112 fn a_declaration_has_nothing_worked_out_about_it() {
1113 assert_eq!(Worked::out(&[("f", declared)]).about("f"), Purity::Opaque);
1114 }
1115
1116 #[test]
1117 fn a_body_this_link_may_replace_has_nothing_worked_out_about_it() {
1118 let mut worked = Worked::linked(Pic::Library, &[("f", only_arithmetic)]);
1121 assert_eq!(worked.about("f"), Purity::Opaque);
1122 let mut worked = Worked::linked(Pic::Executable, &[("f", only_arithmetic)]);
1123 assert_eq!(worked.about("f"), Purity::Const);
1124 }
1125
1126 #[test]
1127 fn nothing_is_written_down_for_a_function_that_came_out_opaque() {
1128 let mut worked = Worked::out(&[("f", declared)]);
1131 assert!(worked.facts.inferred.is_empty());
1132 assert_eq!(worked.about("f"), Purity::Opaque);
1133 }
1134
1135 #[test]
1136 fn what_the_user_declared_and_what_the_body_says_are_both_kept() {
1137 let mut names = Interner::new();
1141 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1142 let mut module = Module::new(names.intern("t.c"), &target);
1143 let mut func = Func::new(names.intern("f"), Signature::new());
1144 func.attrs.set = AttrSet::READNONE;
1145 let entry = func.create_block();
1146 let head = func.create_block();
1147 Builder::new(&mut func, entry).jump(head, &[]);
1148 Builder::new(&mut func, head).jump(head, &[]);
1149 module.add_func(func);
1150 let mut facts = Facts::of_module(&module, &names);
1151 infer(&module, &CallGraph::of(&module, Pic::Executable), &mut facts);
1152 let name = names.intern("f");
1153 assert_eq!(facts.inferred(name), Purity::LoopingConst);
1154 assert_eq!(facts.declared(name), Purity::Const);
1155 assert_eq!(facts.purity_of(Callee::Direct(name)), Purity::Const);
1156 }
1157
1158 #[test]
1159 fn an_answer_travels_as_far_up_the_chain_as_it_holds() {
1160 fn calls_g(names: &mut Interner, func: &mut Func) {
1161 let block = func.create_block();
1162 let mut build = Builder::new(func, block);
1163 calls(&mut build, names, "g");
1164 build.ret(&[]);
1165 }
1166 fn calls_h(names: &mut Interner, func: &mut Func) {
1167 let block = func.create_block();
1168 let mut build = Builder::new(func, block);
1169 calls(&mut build, names, "h");
1170 build.ret(&[]);
1171 }
1172 let mut worked = Worked::out(&[("f", calls_g), ("g", calls_h), ("h", empty)]);
1173 assert_eq!(worked.about("h"), Purity::Const);
1174 assert_eq!(worked.about("g"), Purity::Const);
1175 assert_eq!(worked.about("f"), Purity::Const);
1176 assert_eq!(worked.at_a_call_site("f"), Purity::Const);
1177 }
1178
1179 #[test]
1180 fn a_reader_under_a_writer_makes_the_caller_opaque_and_not_pure() {
1181 fn writes(names: &mut Interner, func: &mut Func) {
1184 let block = func.create_block();
1185 let mut build = Builder::new(func, block);
1186 let at = somewhere(&mut build, names);
1187 let zero = build.iconst(Type::int(32), 0);
1188 build.store(zero, at, access(), Flags::NONE);
1189 build.ret(&[]);
1190 }
1191 fn calls_both(names: &mut Interner, func: &mut Func) {
1192 let block = func.create_block();
1193 let mut build = Builder::new(func, block);
1194 calls(&mut build, names, "g");
1195 calls(&mut build, names, "h");
1196 build.ret(&[]);
1197 }
1198 let mut worked = Worked::out(&[("f", calls_both), ("g", only_arithmetic), ("h", writes)]);
1199 assert_eq!(worked.about("f"), Purity::Opaque);
1200 }
1201}