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 _ => None,
235 }
236 }
237}
238
239#[derive(Debug, Clone, Default)]
245pub struct Facts {
246 declared: HashMap<Symbol, AttrSet>,
247 inferred: HashMap<Symbol, Purity>,
248 from_the_library: HashMap<Symbol, Purity>,
249}
250
251impl Facts {
252 #[must_use]
254 pub fn nothing() -> Self {
255 Self::default()
256 }
257
258 #[must_use]
263 pub fn of_module(module: &Module, names: &Interner) -> Self {
264 let mut facts = Self::default();
265 let mut defined = HashSet::new();
266 for id in module.funcs() {
267 let func = &module[id];
268 facts.declared.insert(func.name, func.attrs.set);
269 if !func.is_declaration() {
270 defined.insert(func.name);
271 }
272 }
273 for &name in facts.declared.keys() {
276 if defined.contains(&name) {
277 continue;
278 }
279 if let Some(purity) = library_purity(names.resolve(name)) {
280 facts.from_the_library.insert(name, purity);
281 }
282 }
283 facts
284 }
285
286 pub fn without_the_library(&mut self) {
291 self.from_the_library.clear();
292 }
293
294 pub fn not_the_library_name(&mut self, name: Symbol) {
299 self.from_the_library.remove(&name);
300 }
301
302 pub fn record_inferred(&mut self, name: Symbol, purity: Purity) {
308 self.inferred.insert(name, purity);
309 }
310
311 #[must_use]
313 pub fn declared(&self, name: Symbol) -> Purity {
314 match self.declared.get(&name) {
315 Some(&set) => from_attributes(set),
316 None => Purity::Opaque,
317 }
318 }
319
320 #[must_use]
322 pub fn inferred(&self, name: Symbol) -> Purity {
323 self.inferred.get(&name).copied().unwrap_or(Purity::Opaque)
324 }
325
326 #[must_use]
330 pub fn purity_of(&self, callee: Callee) -> Purity {
331 match callee {
332 Callee::Direct(name) => self.of_name(name),
333 Callee::Indirect => Purity::Opaque,
336 Callee::Intrinsic(_) => Purity::Opaque,
339 Callee::Asm => Purity::Opaque,
341 }
342 }
343
344 fn of_name(&self, name: Symbol) -> Purity {
346 self.what_was_said_about(name).stronger(self.inferred(name))
347 }
348
349 fn what_was_said_about(&self, name: Symbol) -> Purity {
355 match self.from_the_library.get(&name) {
356 Some(&known) => self.declared(name).stronger(known),
357 None => self.declared(name),
358 }
359 }
360}
361
362pub fn infer(module: &Module, graph: &CallGraph, facts: &mut Facts) {
378 let answers = graph.solve(
382 |_| Purity::Const,
383 |node, answers| match graph.trusted_body(node) {
384 Some(id) if !graph.reaches_unknown(node) => {
389 let purity = what_the_body_does(&module[id], graph, answers, facts);
390 match in_a_cycle(graph, node) {
395 true => purity.weaker(Purity::LoopingConst),
396 false => purity,
397 }
398 }
399 _ => Purity::Opaque,
402 },
403 );
404 for node in graph.nodes() {
405 let purity = answers[node.index()];
406 if purity != Purity::Opaque {
407 facts.record_inferred(graph.name(node), purity);
408 }
409 }
410}
411
412fn what_the_body_does(func: &Func, graph: &CallGraph, answers: &[Purity], facts: &Facts) -> Purity {
418 let mut so_far = Purity::Const;
419 let mut escapes: Option<Escapes> = None;
422 for block in func.blocks() {
423 for inst in func.insts(block) {
424 let data = func[inst];
425 so_far = so_far.weaker(if let Some(callee) = Callee::of(func, inst) {
426 what_that_call_does(callee, graph, answers, facts)
427 } else if !data.opcode.has_effects() || data.opcode.is_terminator() {
428 Purity::Const
431 } else {
432 match data.opcode {
433 Opcode::Alloca => Purity::Const,
436 Opcode::Load if plain(func, data) => {
437 let escapes = escapes.get_or_insert_with(|| Escapes::of(func));
438 match ours(func, escapes, func[data.args][0]) {
439 true => Purity::Const,
440 false => Purity::Pure,
441 }
442 }
443 Opcode::Store if plain(func, data) => {
444 let escapes = escapes.get_or_insert_with(|| Escapes::of(func));
445 match ours(func, escapes, func[data.args][1]) {
446 true => Purity::Const,
447 false => Purity::Opaque,
448 }
449 }
450 _ => Purity::Opaque,
455 }
456 });
457 if so_far == Purity::Opaque {
458 return Purity::Opaque;
459 }
460 }
461 }
462 match has_a_cycle(func) {
464 true => so_far.weaker(Purity::LoopingConst),
465 false => so_far,
466 }
467}
468
469fn what_that_call_does(
476 callee: Callee,
477 graph: &CallGraph,
478 answers: &[Purity],
479 facts: &Facts,
480) -> Purity {
481 let Callee::Direct(name) = callee else {
482 return Purity::Opaque;
485 };
486 let said = facts.what_was_said_about(name);
487 match graph.node(name) {
488 Some(node) => said.stronger(answers[node.index()]),
489 None => said,
493 }
494}
495
496fn plain(func: &Func, data: InstData) -> bool {
502 if data.flags.contains(Flags::VOLATILE) {
503 return false;
504 }
505 match data.extra {
506 Extra::Mem(mem) => func[mem].order == MemOrder::NotAtomic,
507 _ => false,
508 }
509}
510
511fn ours(func: &Func, escapes: &Escapes, pointer: Value) -> bool {
518 matches!(origin(func, pointer).0, Origin::Local(local) if !escapes.escaped(local))
519}
520
521fn in_a_cycle(graph: &CallGraph, node: Node) -> bool {
527 graph.components()[graph.component_of(node)].len() > 1 || graph.calls(node).contains(&node)
528}
529
530fn has_a_cycle(func: &Func) -> bool {
541 let cfg = Cfg::new(func);
542 func.blocks().any(|block| {
543 let Some(from) = cfg.rank(block) else { return false };
544 cfg.successors(block).iter().any(|&to| cfg.rank(to).is_some_and(|to| to <= from))
545 })
546}
547
548fn from_attributes(set: AttrSet) -> Purity {
554 let terminates = !set.contains(AttrSet::NORETURN);
555 if set.contains(AttrSet::READNONE) {
556 return Purity::of(false, terminates);
557 }
558 if set.contains(AttrSet::READONLY) {
559 return Purity::of(true, terminates);
560 }
561 Purity::Opaque
562}
563
564const LIBRARY: &[(&str, Purity)] = &[
573 ("abs", Purity::Const),
574 ("imaxabs", Purity::Const),
575 ("labs", Purity::Const),
576 ("llabs", Purity::Const),
577 ("memchr", Purity::Pure),
578 ("memcmp", Purity::Pure),
579 ("strchr", Purity::Pure),
580 ("strcmp", Purity::Pure),
581 ("strcspn", Purity::Pure),
582 ("strlen", Purity::Pure),
583 ("strncmp", Purity::Pure),
584 ("strnlen", Purity::Pure),
585 ("strpbrk", Purity::Pure),
586 ("strrchr", Purity::Pure),
587 ("strspn", Purity::Pure),
588 ("strstr", Purity::Pure),
589];
590
591fn library_purity(name: &str) -> Option<Purity> {
596 let name = name.strip_prefix("__builtin_").unwrap_or(name);
597 LIBRARY.binary_search_by_key(&name, |&(named, _)| named).ok().map(|at| LIBRARY[at].1)
598}
599
600#[cfg(test)]
601mod tests {
602 use rucc_base::Interner;
603 use rucc_ir::{
604 AsmInfo, AttrSet, BlockCallList, Builder, CallInfo, Extra, Flags, Func, InstData, IntPred,
605 MemInfo, MemOrder, Module, Opcode, Pic, Restrict, Signature, Type, Value,
606 };
607 use rucc_target::{TargetInfo, Triple};
608
609 use super::{CallGraph, Callee, Facts, LIBRARY, Purity, infer};
610
611 fn module(named: &[(&str, bool, AttrSet)]) -> (Interner, Module) {
613 let mut names = Interner::new();
614 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
615 let mut module = Module::new(names.intern("t.c"), &target);
616 for &(name, defined, attrs) in named {
617 let mut func = Func::new(names.intern(name), Signature::new());
618 func.attrs.set = attrs;
619 if defined {
620 let block = func.create_block();
621 let mut build = Builder::new(&mut func, block);
622 let zero = build.iconst(Type::int(32), 0);
623 build.ret(&[zero]);
624 }
625 module.add_func(func);
626 }
627 (names, module)
628 }
629
630 fn purity(names: &mut Interner, module: &Module, name: &str) -> Purity {
632 let facts = Facts::of_module(module, names);
633 let symbol = names.intern(name);
634 facts.purity_of(Callee::Direct(symbol))
635 }
636
637 #[test]
638 fn a_function_nobody_promised_anything_about_is_opaque() {
639 let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
640 assert_eq!(purity(&mut names, &module, "f"), Purity::Opaque);
641 }
642
643 #[test]
644 fn a_name_this_module_never_heard_of_is_opaque_as_well() {
645 let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
646 let facts = Facts::of_module(&module, &names);
647 assert_eq!(facts.purity_of(Callee::Direct(names.intern("g"))), Purity::Opaque);
648 }
649
650 #[test]
651 fn the_const_attribute_is_honoured_because_the_user_asserted_it() {
652 let (mut names, module) = module(&[("f", false, AttrSet::READNONE)]);
653 let purity = purity(&mut names, &module, "f");
654 assert_eq!(purity, Purity::Const);
655 assert!(purity.depends_only_on_arguments());
656 assert!(purity.can_be_deleted_when_unused());
657 }
658
659 #[test]
660 fn the_pure_attribute_reads_memory_and_writes_none() {
661 let (mut names, module) = module(&[("f", false, AttrSet::READONLY)]);
662 let purity = purity(&mut names, &module, "f");
663 assert_eq!(purity, Purity::Pure);
664 assert!(purity.reads_memory());
665 assert!(!purity.writes_memory());
666 assert!(!purity.depends_only_on_arguments());
667 assert!(purity.can_be_deleted_when_unused());
668 }
669
670 #[test]
671 fn a_const_function_that_does_not_come_back_may_not_be_deleted() {
672 let (mut names, module) =
675 module(&[("f", false, AttrSet::READNONE.union(AttrSet::NORETURN))]);
676 let purity = purity(&mut names, &module, "f");
677 assert_eq!(purity, Purity::LoopingConst);
678 assert!(purity.depends_only_on_arguments());
679 assert!(!purity.can_be_deleted_when_unused());
680 }
681
682 #[test]
683 fn nothing_that_is_not_a_direct_call_is_anything_but_opaque() {
684 let (mut names, module) = module(&[("f", true, AttrSet::READNONE)]);
685 let facts = Facts::of_module(&module, &names);
686 assert_eq!(facts.purity_of(Callee::Indirect), Purity::Opaque);
689 assert_eq!(facts.purity_of(Callee::Asm), Purity::Opaque);
690 let vector = names.intern("__builtin_ia32_paddb");
691 assert_eq!(facts.purity_of(Callee::Intrinsic(vector)), Purity::Opaque);
692 }
693
694 #[test]
695 fn the_library_names_are_known_under_both_spellings() {
696 let (mut names, module) = module(&[
697 ("strlen", false, AttrSet::NONE),
698 ("abs", false, AttrSet::NONE),
699 ("__builtin_strlen", false, AttrSet::NONE),
700 ("printf", false, AttrSet::NONE),
701 ]);
702 assert_eq!(purity(&mut names, &module, "strlen"), Purity::Pure);
703 assert_eq!(purity(&mut names, &module, "__builtin_strlen"), Purity::Pure);
704 assert_eq!(purity(&mut names, &module, "abs"), Purity::Const);
705 assert_eq!(purity(&mut names, &module, "printf"), Purity::Opaque);
707 }
708
709 #[test]
710 fn a_module_that_defines_strlen_means_its_own() {
711 let (mut names, module) = module(&[("strlen", true, AttrSet::NONE)]);
712 assert_eq!(purity(&mut names, &module, "strlen"), Purity::Opaque);
713 }
714
715 #[test]
716 fn no_builtin_takes_the_table_away_and_the_named_form_takes_one_entry() {
717 let (mut names, module) =
718 module(&[("strlen", false, AttrSet::NONE), ("abs", false, AttrSet::NONE)]);
719 let mut facts = Facts::of_module(&module, &names);
720 let strlen = names.intern("strlen");
721 let abs = names.intern("abs");
722 facts.not_the_library_name(strlen);
723 assert_eq!(facts.purity_of(Callee::Direct(strlen)), Purity::Opaque);
724 assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Const);
725 facts.without_the_library();
726 assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Opaque);
727 }
728
729 #[test]
730 fn what_the_user_wrote_and_what_analysis_worked_out_are_kept_apart() {
731 let (mut names, module) =
732 module(&[("f", true, AttrSet::READNONE.union(AttrSet::NORETURN))]);
733 let mut facts = Facts::of_module(&module, &names);
734 let f = names.intern("f");
735 assert_eq!(facts.declared(f), Purity::LoopingConst);
736 assert_eq!(facts.inferred(f), Purity::Opaque);
737 facts.record_inferred(f, Purity::Pure);
741 assert_eq!(facts.declared(f), Purity::LoopingConst);
742 assert_eq!(facts.inferred(f), Purity::Pure);
743 assert_eq!(facts.purity_of(Callee::Direct(f)), Purity::Const);
744 }
745
746 #[test]
747 fn what_an_instruction_calls_is_read_off_the_instruction() {
748 let mut names = Interner::new();
749 let mut func = Func::new(names.intern("caller"), Signature::new());
750 let block = func.create_block();
751 let mut build = Builder::new(&mut func, block);
752 let signature = build.func().add_signature(Signature::new());
753 let direct = build.call(names.intern("f"), signature, &[]);
754 let varargs = build.func().push_abis(&[]);
755 let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
756 let indirect = build.inst(
757 InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
758 &[],
759 );
760 let asm = build.inline_asm(
761 AsmInfo {
762 template: names.intern("nop"),
763 constraints: names.intern(""),
764 clobbers: names.intern(""),
765 targets: BlockCallList::EMPTY,
766 },
767 &[],
768 &[],
769 Flags::NONE,
770 );
771 let nothing = build.ret(&[]);
772
773 let f = names.intern("f");
774 assert_eq!(Callee::of(&func, nothing), None);
775 assert_eq!(Callee::of(&func, direct), Some(Callee::Direct(f)));
776 assert_eq!(Callee::of(&func, indirect), Some(Callee::Indirect));
777 assert_eq!(Callee::of(&func, asm), Some(Callee::Asm));
778 }
779
780 #[test]
781 fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
782 for one in Purity::ALL {
783 assert_eq!(one.stronger(one), one, "{one} is not idempotent");
784 assert_eq!(one.weaker(one), one, "{one} is not idempotent");
785 assert_eq!(one.stronger(Purity::Opaque), one, "opaque should say nothing");
786 assert_eq!(one.weaker(Purity::Opaque), Purity::Opaque, "opaque covers everything");
787 for two in Purity::ALL {
788 assert_eq!(one.stronger(two), two.stronger(one), "{one} and {two} disagree");
789 assert_eq!(one.weaker(two), two.weaker(one), "{one} and {two} disagree");
790 let both = one.weaker(two);
792 assert!(both.reads_memory() >= one.reads_memory());
793 assert!(both.writes_memory() >= one.writes_memory());
794 assert!(both.terminates() <= one.terminates());
795 }
796 }
797 }
798
799 #[test]
800 fn only_an_opaque_call_may_write_memory() {
801 for purity in Purity::ALL {
802 assert_eq!(purity.writes_memory(), purity == Purity::Opaque, "{purity}");
803 assert_eq!(purity.can_be_deleted_when_unused(), purity.terminates(), "{purity}");
804 }
805 }
806
807 #[test]
808 fn the_library_table_is_sorted_says_each_name_once_and_writes_no_memory() {
809 for pair in LIBRARY.windows(2) {
812 assert!(pair[0].0 < pair[1].0, "{} and {} are out of order", pair[0].0, pair[1].0);
813 }
814 for &(name, purity) in LIBRARY {
815 assert!(!purity.writes_memory(), "{name} would not be worth an entry");
816 assert!(purity.terminates(), "{name} is in the table to be deletable");
817 assert!(!name.starts_with("__builtin_"), "{name} is reached under both spellings");
818 }
819 }
820
821 fn access() -> MemInfo {
823 MemInfo {
824 size: 4,
825 align: 4,
826 owns: 4,
827 order: MemOrder::NotAtomic,
828 tbaa: None,
829 restrict: Restrict::NONE,
830 }
831 }
832
833 fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
835 let name = names.intern("v");
836 build.value(
837 InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
838 Type::PTR,
839 )
840 }
841
842 fn stack(build: &mut Builder<'_>) -> Value {
844 let mem = build.func().add_mem(access());
845 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
846 }
847
848 fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str) {
850 let name = names.intern(name);
851 let signature = build.func().add_signature(Signature::new());
852 build.call(name, signature, &[]);
853 }
854
855 type Body = fn(&mut Interner, &mut Func);
857
858 struct Worked {
863 names: Interner,
864 facts: Facts,
865 }
866
867 impl Worked {
868 fn out(bodies: &[(&str, Body)]) -> Self {
870 Self::linked(Pic::Executable, bodies)
871 }
872
873 fn linked(pic: Pic, bodies: &[(&str, Body)]) -> Self {
875 let mut names = Interner::new();
876 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
877 let mut module = Module::new(names.intern("t.c"), &target);
878 for &(name, body) in bodies {
879 let mut func = Func::new(names.intern(name), Signature::new());
880 body(&mut names, &mut func);
881 module.add_func(func);
882 }
883 let mut facts = Facts::of_module(&module, &names);
884 infer(&module, &CallGraph::of(&module, pic), &mut facts);
885 Self { names, facts }
886 }
887
888 fn about(&mut self, name: &str) -> Purity {
890 let name = self.names.intern(name);
891 self.facts.inferred(name)
892 }
893
894 fn at_a_call_site(&mut self, name: &str) -> Purity {
896 let name = self.names.intern(name);
897 self.facts.purity_of(Callee::Direct(name))
898 }
899 }
900
901 fn only_arithmetic(_: &mut Interner, func: &mut Func) {
903 let block = func.create_block();
904 let mut build = Builder::new(func, block);
905 let a = build.iconst(Type::int(32), 2);
906 let b = build.iconst(Type::int(32), 3);
907 let sum = build.binary(Opcode::Add, a, b, Flags::NONE);
908 build.ret(&[sum]);
909 }
910
911 fn empty(_: &mut Interner, func: &mut Func) {
913 let block = func.create_block();
914 Builder::new(func, block).ret(&[]);
915 }
916
917 fn declared(_: &mut Interner, _: &mut Func) {}
919
920 #[test]
921 fn a_function_that_only_computes_is_const() {
922 assert_eq!(Worked::out(&[("f", only_arithmetic)]).about("f"), Purity::Const);
923 }
924
925 #[test]
926 fn a_function_that_reads_memory_it_did_not_make_is_pure() {
927 fn reads(names: &mut Interner, func: &mut Func) {
928 let block = func.create_block();
929 let mut build = Builder::new(func, block);
930 let at = somewhere(&mut build, names);
931 let value = build.load(Type::int(32), at, access(), Flags::NONE);
932 build.ret(&[value]);
933 }
934 let mut worked = Worked::out(&[("f", reads)]);
935 assert_eq!(worked.about("f"), Purity::Pure);
936 assert!(worked.about("f").can_be_deleted_when_unused());
937 }
938
939 #[test]
940 fn a_function_that_writes_memory_it_did_not_make_is_opaque() {
941 fn writes(names: &mut Interner, func: &mut Func) {
942 let block = func.create_block();
943 let mut build = Builder::new(func, block);
944 let at = somewhere(&mut build, names);
945 let zero = build.iconst(Type::int(32), 0);
946 build.store(zero, at, access(), Flags::NONE);
947 build.ret(&[]);
948 }
949 assert_eq!(Worked::out(&[("f", writes)]).about("f"), Purity::Opaque);
950 }
951
952 #[test]
953 fn a_local_this_function_kept_to_itself_is_not_memory() {
954 fn temporary(_: &mut Interner, func: &mut Func) {
958 let block = func.create_block();
959 let mut build = Builder::new(func, block);
960 let at = stack(&mut build);
961 let zero = build.iconst(Type::int(32), 0);
962 build.store(zero, at, access(), Flags::NONE);
963 let back = build.load(Type::int(32), at, access(), Flags::NONE);
964 build.ret(&[back]);
965 }
966 assert_eq!(Worked::out(&[("f", temporary)]).about("f"), Purity::Const);
967 }
968
969 #[test]
970 fn a_local_whose_address_the_function_hands_back_is_memory_like_any_other() {
971 fn handed_back(_: &mut Interner, func: &mut Func) {
972 let block = func.create_block();
973 let mut build = Builder::new(func, block);
974 let at = stack(&mut build);
975 let zero = build.iconst(Type::int(32), 0);
976 build.store(zero, at, access(), Flags::NONE);
977 build.ret(&[at]);
978 }
979 assert_eq!(Worked::out(&[("f", handed_back)]).about("f"), Purity::Opaque);
980 }
981
982 #[test]
983 fn a_volatile_read_is_opaque_however_private_the_storage_is() {
984 fn volatile(_: &mut Interner, func: &mut Func) {
987 let block = func.create_block();
988 let mut build = Builder::new(func, block);
989 let at = stack(&mut build);
990 let value = build.load(Type::int(32), at, access(), Flags::VOLATILE);
991 build.ret(&[value]);
992 }
993 assert_eq!(Worked::out(&[("f", volatile)]).about("f"), Purity::Opaque);
994 }
995
996 #[test]
997 fn a_caller_is_what_the_function_it_calls_is() {
998 fn calls_g(names: &mut Interner, func: &mut Func) {
999 let block = func.create_block();
1000 let mut build = Builder::new(func, block);
1001 calls(&mut build, names, "g");
1002 build.ret(&[]);
1003 }
1004 let mut worked = Worked::out(&[("f", calls_g), ("g", only_arithmetic)]);
1005 assert_eq!(worked.about("g"), Purity::Const);
1006 assert_eq!(worked.about("f"), Purity::Const);
1007 }
1008
1009 #[test]
1010 fn a_caller_of_something_nobody_can_see_is_opaque() {
1011 fn calls_g(names: &mut Interner, func: &mut Func) {
1012 let block = func.create_block();
1013 let mut build = Builder::new(func, block);
1014 calls(&mut build, names, "g");
1015 build.ret(&[]);
1016 }
1017 let mut worked = Worked::out(&[("f", calls_g), ("g", declared)]);
1018 assert_eq!(worked.about("g"), Purity::Opaque);
1019 assert_eq!(worked.about("f"), Purity::Opaque);
1020 }
1021
1022 #[test]
1023 fn what_the_library_says_about_a_callee_reaches_the_caller() {
1024 fn calls_strlen(names: &mut Interner, func: &mut Func) {
1025 let block = func.create_block();
1026 let mut build = Builder::new(func, block);
1027 calls(&mut build, names, "strlen");
1028 build.ret(&[]);
1029 }
1030 let mut worked = Worked::out(&[("f", calls_strlen), ("strlen", declared)]);
1031 assert_eq!(worked.about("f"), Purity::Pure);
1032 }
1033
1034 #[test]
1035 fn two_functions_that_call_each_other_and_do_nothing_else_are_not_opaque() {
1036 fn calls_g(names: &mut Interner, func: &mut Func) {
1041 let block = func.create_block();
1042 let mut build = Builder::new(func, block);
1043 calls(&mut build, names, "g");
1044 build.ret(&[]);
1045 }
1046 fn calls_f(names: &mut Interner, func: &mut Func) {
1047 let block = func.create_block();
1048 let mut build = Builder::new(func, block);
1049 calls(&mut build, names, "f");
1050 build.ret(&[]);
1051 }
1052 let mut worked = Worked::out(&[("f", calls_g), ("g", calls_f)]);
1053 assert_eq!(worked.about("f"), Purity::LoopingConst);
1054 assert_eq!(worked.about("g"), Purity::LoopingConst);
1055 assert!(worked.about("f").depends_only_on_arguments());
1056 assert!(!worked.about("f").can_be_deleted_when_unused());
1057 }
1058
1059 #[test]
1060 fn a_function_that_calls_itself_may_not_come_back() {
1061 fn calls_itself(names: &mut Interner, func: &mut Func) {
1062 let block = func.create_block();
1063 let mut build = Builder::new(func, block);
1064 calls(&mut build, names, "f");
1065 build.ret(&[]);
1066 }
1067 assert_eq!(Worked::out(&[("f", calls_itself)]).about("f"), Purity::LoopingConst);
1068 }
1069
1070 #[test]
1071 fn a_function_with_a_loop_in_it_may_not_come_back() {
1072 fn loops(_: &mut Interner, func: &mut Func) {
1075 let entry = func.create_block();
1076 let head = func.create_block();
1077 let done = func.create_block();
1078 let mut build = Builder::new(func, entry);
1079 build.jump(head, &[]);
1080 let mut build = Builder::new(func, head);
1081 let zero = build.iconst(Type::int(32), 0);
1082 let cond = build.icmp(IntPred::Eq, zero, zero);
1083 build.br_if(cond, head, &[], done, &[]);
1084 Builder::new(func, done).ret(&[]);
1085 }
1086 let mut worked = Worked::out(&[("f", loops)]);
1087 assert_eq!(worked.about("f"), Purity::LoopingConst);
1088 assert!(!worked.about("f").can_be_deleted_when_unused());
1089 }
1090
1091 #[test]
1092 fn a_branch_that_joins_again_is_not_a_loop() {
1093 fn branches(_: &mut Interner, func: &mut Func) {
1096 let entry = func.create_block();
1097 let arm = func.create_block();
1098 let join = func.create_block();
1099 let mut build = Builder::new(func, entry);
1100 let zero = build.iconst(Type::int(32), 0);
1101 let cond = build.icmp(IntPred::Eq, zero, zero);
1102 build.br_if(cond, arm, &[], join, &[]);
1103 Builder::new(func, arm).jump(join, &[]);
1104 Builder::new(func, join).ret(&[]);
1105 }
1106 assert_eq!(Worked::out(&[("f", branches)]).about("f"), Purity::Const);
1107 }
1108
1109 #[test]
1110 fn a_declaration_has_nothing_worked_out_about_it() {
1111 assert_eq!(Worked::out(&[("f", declared)]).about("f"), Purity::Opaque);
1112 }
1113
1114 #[test]
1115 fn a_body_this_link_may_replace_has_nothing_worked_out_about_it() {
1116 let mut worked = Worked::linked(Pic::Library, &[("f", only_arithmetic)]);
1119 assert_eq!(worked.about("f"), Purity::Opaque);
1120 let mut worked = Worked::linked(Pic::Executable, &[("f", only_arithmetic)]);
1121 assert_eq!(worked.about("f"), Purity::Const);
1122 }
1123
1124 #[test]
1125 fn nothing_is_written_down_for_a_function_that_came_out_opaque() {
1126 let mut worked = Worked::out(&[("f", declared)]);
1129 assert!(worked.facts.inferred.is_empty());
1130 assert_eq!(worked.about("f"), Purity::Opaque);
1131 }
1132
1133 #[test]
1134 fn what_the_user_declared_and_what_the_body_says_are_both_kept() {
1135 let mut names = Interner::new();
1139 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1140 let mut module = Module::new(names.intern("t.c"), &target);
1141 let mut func = Func::new(names.intern("f"), Signature::new());
1142 func.attrs.set = AttrSet::READNONE;
1143 let entry = func.create_block();
1144 let head = func.create_block();
1145 Builder::new(&mut func, entry).jump(head, &[]);
1146 Builder::new(&mut func, head).jump(head, &[]);
1147 module.add_func(func);
1148 let mut facts = Facts::of_module(&module, &names);
1149 infer(&module, &CallGraph::of(&module, Pic::Executable), &mut facts);
1150 let name = names.intern("f");
1151 assert_eq!(facts.inferred(name), Purity::LoopingConst);
1152 assert_eq!(facts.declared(name), Purity::Const);
1153 assert_eq!(facts.purity_of(Callee::Direct(name)), Purity::Const);
1154 }
1155
1156 #[test]
1157 fn an_answer_travels_as_far_up_the_chain_as_it_holds() {
1158 fn calls_g(names: &mut Interner, func: &mut Func) {
1159 let block = func.create_block();
1160 let mut build = Builder::new(func, block);
1161 calls(&mut build, names, "g");
1162 build.ret(&[]);
1163 }
1164 fn calls_h(names: &mut Interner, func: &mut Func) {
1165 let block = func.create_block();
1166 let mut build = Builder::new(func, block);
1167 calls(&mut build, names, "h");
1168 build.ret(&[]);
1169 }
1170 let mut worked = Worked::out(&[("f", calls_g), ("g", calls_h), ("h", empty)]);
1171 assert_eq!(worked.about("h"), Purity::Const);
1172 assert_eq!(worked.about("g"), Purity::Const);
1173 assert_eq!(worked.about("f"), Purity::Const);
1174 assert_eq!(worked.at_a_call_site("f"), Purity::Const);
1175 }
1176
1177 #[test]
1178 fn a_reader_under_a_writer_makes_the_caller_opaque_and_not_pure() {
1179 fn writes(names: &mut Interner, func: &mut Func) {
1182 let block = func.create_block();
1183 let mut build = Builder::new(func, block);
1184 let at = somewhere(&mut build, names);
1185 let zero = build.iconst(Type::int(32), 0);
1186 build.store(zero, at, access(), Flags::NONE);
1187 build.ret(&[]);
1188 }
1189 fn calls_both(names: &mut Interner, func: &mut Func) {
1190 let block = func.create_block();
1191 let mut build = Builder::new(func, block);
1192 calls(&mut build, names, "g");
1193 calls(&mut build, names, "h");
1194 build.ret(&[]);
1195 }
1196 let mut worked = Worked::out(&[("f", calls_both), ("g", only_arithmetic), ("h", writes)]);
1197 assert_eq!(worked.about("f"), Purity::Opaque);
1198 }
1199}