1use std::collections::HashMap;
42
43use rucc_base::Symbol;
44use rucc_ir::{AttrSet, Block, Def, Func, Inst, Module, Opcode, Value};
45
46use crate::alias::{Escapes, Origin, keeps_address, origin};
47use crate::callgraph::{CallGraph, Node};
48use crate::purity::Callee;
49
50const MAX_STEPS: usize = 25_000;
58
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
66pub enum Effect {
67 #[default]
69 Nothing,
70 Reads,
72 Writes,
74}
75
76impl Effect {
77 #[must_use]
79 pub fn and_then(self, other: Self) -> Self {
80 self.max(other)
81 }
82
83 #[must_use]
85 pub fn as_well_as(self, other: Self) -> Self {
86 self.min(other)
87 }
88
89 #[must_use]
91 pub fn reads(self) -> bool {
92 self != Self::Nothing
93 }
94
95 #[must_use]
97 pub fn writes(self) -> bool {
98 self == Self::Writes
99 }
100
101 #[must_use]
103 pub fn name(self) -> &'static str {
104 match self {
105 Self::Nothing => "nothing",
106 Self::Reads => "reads",
107 Self::Writes => "writes",
108 }
109 }
110}
111
112#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
114pub struct Touch {
115 pub effect: Effect,
117 pub escapes: bool,
120}
121
122impl Touch {
123 #[must_use]
125 pub fn nothing() -> Self {
126 Self::default()
127 }
128
129 #[must_use]
131 pub fn everything() -> Self {
132 Self { effect: Effect::Writes, escapes: true }
133 }
134
135 #[must_use]
137 pub fn and_then(self, other: Self) -> Self {
138 Self { effect: self.effect.and_then(other.effect), escapes: self.escapes || other.escapes }
139 }
140
141 #[must_use]
143 pub fn as_well_as(self, other: Self) -> Self {
144 Self {
145 effect: self.effect.as_well_as(other.effect),
146 escapes: self.escapes && other.escapes,
147 }
148 }
149}
150
151#[derive(Clone, Debug, PartialEq, Eq)]
153pub struct Summary {
154 outside: Effect,
155 params: Box<[Touch]>,
156}
157
158impl Summary {
159 #[must_use]
161 pub fn nothing(arity: usize) -> Self {
162 Self { outside: Effect::Nothing, params: vec![Touch::nothing(); arity].into() }
163 }
164
165 #[must_use]
167 pub fn everything(arity: usize) -> Self {
168 Self { outside: Effect::Writes, params: vec![Touch::everything(); arity].into() }
169 }
170
171 #[must_use]
176 pub fn doing(outside: Effect, params: &[Touch]) -> Self {
177 Self { outside, params: params.into() }
178 }
179
180 #[must_use]
186 pub fn reading(arity: usize) -> Self {
187 Self::doing(Effect::Reads, &vec![Touch { effect: Effect::Reads, escapes: true }; arity])
188 }
189
190 #[must_use]
196 pub fn through_arguments(arity: usize) -> Self {
197 Self::doing(Effect::Nothing, &vec![Touch::everything(); arity])
198 }
199
200 #[must_use]
203 pub fn outside(&self) -> Effect {
204 self.outside
205 }
206
207 #[must_use]
212 pub fn param(&self, index: usize) -> Touch {
213 self.params.get(index).copied().unwrap_or_else(Touch::everything)
214 }
215
216 #[must_use]
218 pub fn arity(&self) -> usize {
219 self.params.len()
220 }
221
222 #[must_use]
228 pub fn only_through_arguments(&self) -> bool {
229 self.outside == Effect::Nothing
230 }
231
232 #[must_use]
234 pub fn writes_nothing(&self) -> bool {
235 !self.outside.writes() && self.params.iter().all(|touch| !touch.effect.writes())
236 }
237
238 #[must_use]
240 pub fn touches_nothing(&self) -> bool {
241 self.outside == Effect::Nothing
242 && self.params.iter().all(|touch| touch.effect == Effect::Nothing)
243 }
244
245 #[must_use]
247 fn as_well_as(&self, other: &Self) -> Self {
248 let arity = self.params.len().max(other.params.len());
249 let params = (0..arity).map(|at| self.param(at).as_well_as(other.param(at))).collect();
250 Self { outside: self.outside.as_well_as(other.outside), params }
251 }
252
253 fn touch_everything(&mut self) {
255 self.outside = Effect::Writes;
256 for touch in &mut self.params {
257 *touch = Touch::everything();
258 }
259 }
260}
261
262#[derive(Clone, Debug, Default)]
268pub struct Summaries {
269 known: HashMap<Symbol, Summary>,
270}
271
272impl Summaries {
273 #[must_use]
275 pub fn nothing() -> Self {
276 Self::default()
277 }
278
279 #[must_use]
287 pub fn of_module(module: &Module) -> Self {
288 let mut summaries = Self::default();
289 for id in module.funcs() {
290 let func = &module[id];
291 let arity = func.signature().params.len();
292 if let Some(summary) = from_attributes(func.attrs.set, arity) {
293 summaries.known.insert(func.name, summary);
294 }
295 }
296 summaries
297 }
298
299 #[must_use]
301 pub fn of(&self, name: Symbol) -> Option<&Summary> {
302 self.known.get(&name)
303 }
304
305 #[must_use]
310 pub fn at(&self, func: &Func, call: Inst) -> Option<&Summary> {
311 match Callee::of(func, call)? {
312 Callee::Direct(name) => self.of(name),
313 Callee::Indirect | Callee::Intrinsic(_) | Callee::Asm => None,
314 }
315 }
316
317 pub fn record(&mut self, name: Symbol, summary: Summary) {
323 let merged = match self.known.get(&name) {
324 Some(said) => said.as_well_as(&summary),
325 None => summary,
326 };
327 self.known.insert(name, merged);
328 }
329
330 #[must_use]
332 pub fn len(&self) -> usize {
333 self.known.len()
334 }
335
336 #[must_use]
338 pub fn is_empty(&self) -> bool {
339 self.known.is_empty()
340 }
341}
342
343fn from_attributes(set: AttrSet, arity: usize) -> Option<Summary> {
351 if set.contains(AttrSet::READNONE) {
352 let params = vec![Touch { effect: Effect::Nothing, escapes: true }; arity];
353 return Some(Summary { outside: Effect::Nothing, params: params.into() });
354 }
355 if set.contains(AttrSet::READONLY) {
359 return Some(Summary::reading(arity));
360 }
361 if set.contains(AttrSet::ARGMEM_ONLY) {
363 return Some(Summary::through_arguments(arity));
364 }
365 None
366}
367
368pub fn summarize(module: &Module, graph: &CallGraph, summaries: &mut Summaries) {
374 let arity = |node: Node| match graph.func(node) {
375 Some(id) => module[id].signature().params.len(),
376 None => 0,
377 };
378 let answers = graph.solve(
379 |node| Summary::nothing(arity(node)),
380 |node, answers| match graph.trusted_body(node) {
381 Some(id) => what_the_body_does(&module[id], graph, answers, summaries),
382 None => match summaries.of(graph.name(node)) {
385 Some(said) => said.clone(),
386 None => Summary::everything(arity(node)),
387 },
388 },
389 );
390 for node in graph.nodes() {
391 if graph.trusted_body(node).is_none() {
392 continue;
393 }
394 summaries.record(graph.name(node), answers[node.index()].clone());
395 }
396}
397
398fn what_the_body_does(
400 func: &Func,
401 graph: &CallGraph,
402 answers: &[Summary],
403 said: &Summaries,
404) -> Summary {
405 let arity = func.signature().params.len();
406 let Some(entry) = func.entry() else { return Summary::everything(arity) };
407 if func[entry].params.len() != arity {
411 return Summary::everything(arity);
412 }
413 let mut callees: HashMap<Inst, Summary> = HashMap::new();
418 let mut steps = 0;
419 for block in func.blocks() {
420 for inst in func.insts(block) {
421 steps += 1;
422 if steps > MAX_STEPS {
423 return Summary::everything(arity);
424 }
425 if let Some(summary) = what_that_call_does(func, inst, graph, answers, said) {
426 callees.insert(inst, summary);
427 }
428 }
429 }
430 let escapes = Escapes::with(func, |inst, index| {
431 callees.get(&inst).is_some_and(|summary| !summary.param(index).escapes)
432 });
433 let mut summary = Summary::nothing(arity);
434 for block in func.blocks() {
435 for inst in func.insts(block) {
436 add_block_escapes(func, entry, &mut summary, inst);
439 if let Some(callee) = callees.get(&inst) {
440 add_call(func, entry, &escapes, &mut summary, inst, callee);
441 continue;
442 }
443 add_access(func, entry, &escapes, &mut summary, inst);
444 add_operand_escapes(func, entry, &mut summary, inst);
445 }
446 }
447 summary
448}
449
450fn what_that_call_does(
452 func: &Func,
453 inst: Inst,
454 graph: &CallGraph,
455 answers: &[Summary],
456 said: &Summaries,
457) -> Option<Summary> {
458 let callee = Callee::of(func, inst)?;
459 let direct = matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall);
463 let Callee::Direct(name) = callee else {
464 return Some(Summary::everything(0));
466 };
467 if !direct {
468 return Some(Summary::everything(0));
469 }
470 let walked = graph.node(name).map(|node| answers[node.index()].clone());
474 let promised = said.of(name).cloned();
475 Some(match (walked, promised) {
476 (Some(walked), Some(promised)) => walked.as_well_as(&promised),
477 (Some(only), None) | (None, Some(only)) => only,
478 (None, None) => Summary::everything(0),
479 })
480}
481
482fn add_call(
484 func: &Func,
485 entry: Block,
486 escapes: &Escapes,
487 summary: &mut Summary,
488 inst: Inst,
489 callee: &Summary,
490) {
491 summary.outside = summary.outside.and_then(callee.outside());
492 let args = &func[func[inst].args];
493 for (at, &arg) in args.iter().enumerate() {
494 if !func[arg].ty.is_ptr() {
495 continue;
496 }
497 let touch = callee.param(at);
498 if touch == Touch::nothing() {
499 continue;
500 }
501 match behind(func, entry, escapes, arg) {
502 Behind::Param(at) => summary.params[at] = summary.params[at].and_then(touch),
507 Behind::Private => {}
510 Behind::Outside => summary.outside = summary.outside.and_then(touch.effect),
511 }
512 }
513}
514
515fn add_access(func: &Func, entry: Block, escapes: &Escapes, summary: &mut Summary, inst: Inst) {
517 let data = func[inst];
518 if !data.opcode.has_effects() || data.opcode.is_terminator() {
519 return;
520 }
521 if data.opcode.touches_only_planes() {
524 return;
525 }
526 let args = &func[data.args];
527 let mut through = |at: usize, effect: Effect| match behind(func, entry, escapes, args[at]) {
528 Behind::Param(at) => {
529 summary.params[at].effect = summary.params[at].effect.and_then(effect);
530 }
531 Behind::Private => {}
532 Behind::Outside => summary.outside = summary.outside.and_then(effect),
533 };
534 match data.opcode {
535 Opcode::Alloca => {}
537 Opcode::Load | Opcode::AtomicLoad | Opcode::Prefetch => through(0, Effect::Reads),
538 Opcode::Store | Opcode::AtomicStore => through(1, Effect::Writes),
539 Opcode::AtomicRmw | Opcode::Cmpxchg | Opcode::Memset => through(0, Effect::Writes),
540 Opcode::Memcpy | Opcode::Memmove => {
541 through(0, Effect::Writes);
542 through(1, Effect::Reads);
543 }
544 _ => summary.touch_everything(),
548 }
549}
550
551fn add_operand_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
558 let data = func[inst];
559 for (at, &arg) in func[data.args].iter().enumerate() {
560 if keeps_address(data.opcode, at) {
561 continue;
562 }
563 if let Some(at) = param_behind(func, entry, arg) {
564 summary.params[at].escapes = true;
565 }
566 }
567}
568
569fn add_block_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
572 for call in func.successors(inst) {
573 for &arg in &func[call.args] {
574 if let Some(at) = param_behind(func, entry, arg) {
575 summary.params[at].escapes = true;
576 }
577 }
578 }
579}
580
581#[derive(Clone, Copy, Debug, PartialEq, Eq)]
583enum Behind {
584 Param(usize),
586 Private,
588 Outside,
590}
591
592fn behind(func: &Func, entry: Block, escapes: &Escapes, pointer: Value) -> Behind {
593 match origin(func, pointer).0 {
594 Origin::Local(local) if !escapes.escaped(local) => Behind::Private,
595 Origin::Unknown(value) => match param_of(func, entry, value) {
596 Some(at) => Behind::Param(at),
597 None => Behind::Outside,
598 },
599 _ => Behind::Outside,
600 }
601}
602
603fn param_behind(func: &Func, entry: Block, pointer: Value) -> Option<usize> {
605 let Origin::Unknown(value) = origin(func, pointer).0 else { return None };
606 param_of(func, entry, value)
607}
608
609fn param_of(func: &Func, entry: Block, value: Value) -> Option<usize> {
610 match func[value].def {
611 Def::Param { block, index } if block == entry => Some(index as usize),
612 _ => None,
613 }
614}
615
616#[cfg(test)]
617mod tests {
618 use rucc_base::Interner;
619 use rucc_ir::{
620 Builder, Extra, Flags, InstData, MemInfo, MemOrder, Pic, Restrict, Signature, Type,
621 };
622 use rucc_target::{TargetInfo, Triple};
623
624 use super::{
625 AttrSet, CallGraph, Effect, Func, Module, Opcode, Summaries, Summary, Touch, Value,
626 summarize,
627 };
628
629 fn access() -> MemInfo {
631 MemInfo {
632 size: 4,
633 align: 4,
634 owns: 4,
635 order: MemOrder::NotAtomic,
636 tbaa: None,
637 restrict: Restrict::NONE,
638 }
639 }
640
641 fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
643 let name = names.intern("v");
644 build.value(
645 InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
646 Type::PTR,
647 )
648 }
649
650 fn stack(build: &mut Builder<'_>) -> Value {
652 let mem = build.func().add_mem(access());
653 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
654 }
655
656 fn reads(build: &mut Builder<'_>, addr: Value) -> Value {
658 build.load(Type::int(32), addr, access(), Flags::NONE)
659 }
660
661 fn writes(build: &mut Builder<'_>, addr: Value) {
663 let zero = build.iconst(Type::int(32), 0);
664 build.store(zero, addr, access(), Flags::NONE);
665 }
666
667 fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str, args: &[Value]) {
669 let name = names.intern(name);
670 let params = vec![Type::PTR; args.len()];
671 let signature = build.func().add_signature(Signature::new().with_params(¶ms));
672 build.call(name, signature, args);
673 }
674
675 type Body = fn(&mut Interner, &mut Builder<'_>, &[Value]);
677
678 struct Worked {
680 names: Interner,
681 summaries: Summaries,
682 }
683
684 impl Worked {
685 fn out(bodies: &[(&str, usize, AttrSet, Option<Body>)]) -> Self {
687 let mut names = Interner::new();
688 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
689 let mut module = Module::new(names.intern("t.c"), &target);
690 for &(name, arity, attrs, body) in bodies {
691 let params = vec![Type::PTR; arity];
692 let mut func = Func::new(names.intern(name), Signature::new().with_params(¶ms));
693 func.attrs.set = attrs;
694 if let Some(body) = body {
695 let entry = func.create_block();
696 let args: Vec<Value> =
697 (0..arity).map(|_| func.append_param(entry, Type::PTR)).collect();
698 let mut build = Builder::new(&mut func, entry);
699 body(&mut names, &mut build, &args);
700 }
701 module.add_func(func);
702 }
703 let mut summaries = Summaries::of_module(&module);
704 summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
705 Self { names, summaries }
706 }
707
708 fn about(&mut self, name: &str) -> Summary {
710 let name = self.names.intern(name);
711 self.summaries.of(name).expect("a defined function has a summary").clone()
712 }
713 }
714
715 fn nothing(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
717 build.ret(&[]);
718 }
719
720 #[test]
721 fn a_body_that_goes_nowhere_near_memory_says_so() {
722 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(nothing))]);
723 let f = worked.about("f");
724 assert!(f.touches_nothing());
725 assert!(f.writes_nothing());
726 assert!(f.only_through_arguments());
727 assert_eq!(f.arity(), 2);
728 assert_eq!(f.param(0), Touch::nothing());
729 assert_eq!(f.param(1), Touch::nothing());
730 }
731
732 #[test]
733 fn a_load_through_one_parameter_is_a_read_of_that_one() {
734 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
735 let value = reads(build, args[0]);
736 build.ret(&[value]);
737 }
738 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
739 let f = worked.about("f");
740 assert_eq!(f.param(0).effect, Effect::Reads);
741 assert_eq!(f.param(1).effect, Effect::Nothing);
742 assert_eq!(f.outside(), Effect::Nothing);
743 assert!(f.writes_nothing());
744 assert!(f.only_through_arguments());
745 assert!(!f.param(0).escapes, "dereferencing an address is not keeping it");
746 }
747
748 #[test]
749 fn a_store_through_one_parameter_is_a_write_of_that_one() {
750 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
751 writes(build, args[1]);
752 build.ret(&[]);
753 }
754 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
755 let f = worked.about("f");
756 assert_eq!(f.param(0).effect, Effect::Nothing);
757 assert_eq!(f.param(1).effect, Effect::Writes);
758 assert!(!f.writes_nothing());
759 assert!(f.only_through_arguments(), "the only thing it wrote, it was handed");
760 }
761
762 #[test]
763 fn a_global_is_not_anybody_s_parameter() {
764 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
765 let global = somewhere(build, names);
766 writes(build, global);
767 build.ret(&[]);
768 }
769 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
770 let f = worked.about("f");
771 assert_eq!(f.outside(), Effect::Writes);
772 assert_eq!(f.param(0), Touch::nothing());
773 assert!(!f.only_through_arguments());
774 }
775
776 #[test]
777 fn what_a_function_did_to_its_own_stack_is_nobody_else_s_business() {
778 fn body(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
779 let local = stack(build);
780 writes(build, local);
781 let value = reads(build, local);
782 build.ret(&[value]);
783 }
784 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
785 assert!(worked.about("f").touches_nothing());
786 }
787
788 #[test]
789 fn a_copy_writes_the_one_it_writes_and_reads_the_one_it_reads() {
790 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
791 let mem = build.func().add_mem(access());
792 let list = build.func().push_values(&[args[0], args[1]]);
793 build.inst(
794 InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::Memcpy) },
795 &[],
796 );
797 build.ret(&[]);
798 }
799 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
800 let f = worked.about("f");
801 assert_eq!(f.param(0).effect, Effect::Writes);
802 assert_eq!(f.param(1).effect, Effect::Reads);
803 assert!(f.only_through_arguments());
804 assert!(!f.param(0).escapes);
805 assert!(!f.param(1).escapes);
806 }
807
808 #[test]
809 fn an_opcode_this_was_not_written_for_did_everything() {
810 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
813 let mem = build.func().add_mem(access());
814 let list = build.func().push_values(&[args[0]]);
815 build.inst(
816 InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::VaStart) },
817 &[],
818 );
819 build.ret(&[]);
820 }
821 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
822 let f = worked.about("f");
823 assert_eq!(f.outside(), Effect::Writes);
824 assert_eq!(f.param(0), Touch::everything());
825 }
826
827 #[test]
828 fn what_the_callee_does_to_what_it_was_handed_is_what_the_caller_does() {
829 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
830 writes(build, args[0]);
831 build.ret(&[]);
832 }
833 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
834 calls(build, names, "callee", &[args[1]]);
835 build.ret(&[]);
836 }
837 let mut worked = Worked::out(&[
838 ("callee", 1, AttrSet::NONE, Some(callee)),
839 ("caller", 2, AttrSet::NONE, Some(caller)),
840 ]);
841 let caller = worked.about("caller");
842 assert_eq!(caller.param(0), Touch::nothing());
845 assert_eq!(caller.param(1).effect, Effect::Writes);
846 assert_eq!(caller.outside(), Effect::Nothing);
847 assert!(caller.only_through_arguments());
848 }
849
850 #[test]
851 fn a_parameter_handed_to_something_that_does_not_keep_it_has_not_got_out() {
852 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
855 let value = reads(build, args[0]);
856 build.ret(&[value]);
857 }
858 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
859 calls(build, names, "callee", &[args[0]]);
860 build.ret(&[]);
861 }
862 let mut worked = Worked::out(&[
863 ("callee", 1, AttrSet::NONE, Some(callee)),
864 ("caller", 1, AttrSet::NONE, Some(caller)),
865 ]);
866 assert!(!worked.about("callee").param(0).escapes);
867 assert!(!worked.about("caller").param(0).escapes);
868 }
869
870 #[test]
871 fn a_parameter_written_down_somewhere_has_got_out() {
872 fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
873 let global = somewhere(build, names);
874 build.store(args[0], global, access(), Flags::NONE);
875 build.ret(&[]);
876 }
877 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
878 let f = worked.about("f");
879 assert!(f.param(0).escapes);
880 assert_eq!(f.param(0).effect, Effect::Nothing);
882 assert_eq!(f.outside(), Effect::Writes);
883 }
884
885 #[test]
886 fn a_parameter_a_caller_cannot_be_told_about_travels_up_as_a_write_of_everything() {
887 fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
888 let global = somewhere(build, names);
889 build.store(args[0], global, access(), Flags::NONE);
890 build.ret(&[]);
891 }
892 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
893 calls(build, names, "callee", &[args[0]]);
894 build.ret(&[]);
895 }
896 let mut worked = Worked::out(&[
897 ("callee", 1, AttrSet::NONE, Some(callee)),
898 ("caller", 1, AttrSet::NONE, Some(caller)),
899 ]);
900 let caller = worked.about("caller");
901 assert!(caller.param(0).escapes, "the callee kept it, so the caller let it go");
902 assert_eq!(caller.outside(), Effect::Writes);
903 }
904
905 #[test]
906 fn what_a_callee_did_to_a_local_it_was_only_lent_stays_inside() {
907 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
912 writes(build, args[0]);
913 build.ret(&[]);
914 }
915 fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
916 let place = stack(build);
917 calls(build, names, "callee", &[place]);
918 build.ret(&[]);
919 }
920 let mut worked = Worked::out(&[
921 ("callee", 1, AttrSet::NONE, Some(callee)),
922 ("caller", 0, AttrSet::NONE, Some(caller)),
923 ]);
924 assert_eq!(worked.about("callee").param(0).effect, Effect::Writes);
925 assert!(worked.about("caller").touches_nothing());
926 }
927
928 #[test]
929 fn a_local_the_callee_wrote_down_is_one_this_function_lost() {
930 fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
933 let global = somewhere(build, names);
934 build.store(args[0], global, access(), Flags::NONE);
935 build.ret(&[]);
936 }
937 fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
938 let place = stack(build);
939 calls(build, names, "callee", &[place]);
940 writes(build, place);
941 build.ret(&[]);
942 }
943 let mut worked = Worked::out(&[
944 ("callee", 1, AttrSet::NONE, Some(callee)),
945 ("caller", 0, AttrSet::NONE, Some(caller)),
946 ]);
947 assert!(worked.about("callee").param(0).escapes);
948 assert_eq!(worked.about("caller").outside(), Effect::Writes);
949 }
950
951 #[test]
952 fn a_call_through_an_address_did_everything_to_everything() {
953 fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
954 calls(build, names, "unknown", &[args[0]]);
955 build.ret(&[]);
956 }
957 let mut worked = Worked::out(&[
958 ("unknown", 1, AttrSet::NONE, None),
959 ("f", 1, AttrSet::NONE, Some(body)),
960 ]);
961 let f = worked.about("f");
962 assert_eq!(f.outside(), Effect::Writes);
963 assert_eq!(f.param(0), Touch::everything());
964 }
965
966 #[test]
967 fn two_functions_that_call_each_other_and_touch_nothing_touch_nothing() {
968 fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
972 calls(build, names, "pong", &[args[0]]);
973 build.ret(&[]);
974 }
975 fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
976 calls(build, names, "ping", &[args[0]]);
977 build.ret(&[]);
978 }
979 let mut worked = Worked::out(&[
980 ("ping", 1, AttrSet::NONE, Some(ping)),
981 ("pong", 1, AttrSet::NONE, Some(pong)),
982 ]);
983 assert!(worked.about("ping").touches_nothing());
984 assert!(worked.about("pong").touches_nothing());
985 }
986
987 #[test]
988 fn a_write_inside_a_cycle_is_still_found() {
989 fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
990 calls(build, names, "pong", &[args[0]]);
991 build.ret(&[]);
992 }
993 fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
994 writes(build, args[0]);
995 calls(build, names, "ping", &[args[0]]);
996 build.ret(&[]);
997 }
998 let mut worked = Worked::out(&[
999 ("ping", 1, AttrSet::NONE, Some(ping)),
1000 ("pong", 1, AttrSet::NONE, Some(pong)),
1001 ]);
1002 assert_eq!(worked.about("ping").param(0).effect, Effect::Writes);
1003 assert_eq!(worked.about("pong").param(0).effect, Effect::Writes);
1004 assert!(worked.about("ping").only_through_arguments());
1005 }
1006
1007 #[test]
1008 fn a_declaration_is_whatever_it_promised_and_nothing_more() {
1009 let mut worked = Worked::out(&[
1010 ("plain", 1, AttrSet::NONE, None),
1011 ("none", 1, AttrSet::READNONE, None),
1012 ("only", 1, AttrSet::READONLY, None),
1013 ("args", 1, AttrSet::ARGMEM_ONLY, None),
1014 ]);
1015 let names = worked.names.intern("plain");
1016 assert!(worked.summaries.of(names).is_none(), "nobody promised anything about it");
1017 assert!(worked.about("none").touches_nothing());
1018 let only = worked.about("only");
1019 assert!(only.writes_nothing());
1020 assert_eq!(only.outside(), Effect::Reads);
1021 assert_eq!(only.param(0).effect, Effect::Reads);
1022 let args = worked.about("args");
1023 assert!(args.only_through_arguments());
1024 assert!(!args.writes_nothing());
1025 assert_eq!(args.param(0), Touch::everything());
1026 }
1027
1028 #[test]
1029 fn no_attribute_promises_the_address_was_not_kept() {
1030 let mut worked = Worked::out(&[
1033 ("none", 1, AttrSet::READNONE, None),
1034 ("only", 1, AttrSet::READONLY, None),
1035 ("args", 1, AttrSet::ARGMEM_ONLY, None),
1036 ]);
1037 for name in ["none", "only", "args"] {
1038 assert!(worked.about(name).param(0).escapes, "{name} promised no such thing");
1039 }
1040 }
1041
1042 #[test]
1043 fn a_promise_the_body_does_not_keep_is_still_a_promise() {
1044 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
1048 let global = somewhere(build, names);
1049 writes(build, global);
1050 build.ret(&[]);
1051 }
1052 let mut worked = Worked::out(&[("f", 1, AttrSet::READNONE, Some(body))]);
1053 assert!(worked.about("f").touches_nothing());
1054 }
1055
1056 #[test]
1057 fn an_entry_block_that_does_not_match_the_signature_gets_no_answer() {
1058 let mut names = Interner::new();
1063 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1064 let mut module = Module::new(names.intern("t.c"), &target);
1065 let params = vec![Type::PTR; 2];
1066 let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
1067 let entry = func.create_block();
1068 let only = func.append_param(entry, Type::PTR);
1069 let mut build = Builder::new(&mut func, entry);
1070 build.ret(&[only]);
1071 module.add_func(func);
1072
1073 let mut summaries = Summaries::of_module(&module);
1074 summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
1075 let f = summaries.of(names.intern("f")).expect("a defined function has a summary");
1076 assert_eq!(*f, Summary::everything(2));
1077 }
1078
1079 #[test]
1080 fn a_position_no_parameter_stands_for_is_a_position_anything_happened_to() {
1081 let summary = Summary::nothing(1);
1084 assert_eq!(summary.param(0), Touch::nothing());
1085 assert_eq!(summary.param(1), Touch::everything());
1086 assert_eq!(summary.param(9), Touch::everything());
1087 }
1088
1089 #[test]
1090 fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
1091 let all = [Effect::Nothing, Effect::Reads, Effect::Writes];
1092 for one in all {
1093 assert_eq!(one.and_then(one), one, "{one:?} is not idempotent");
1094 assert_eq!(one.as_well_as(one), one, "{one:?} is not idempotent");
1095 assert_eq!(one.and_then(Effect::Nothing), one, "nothing happening changes nothing");
1096 assert_eq!(one.as_well_as(Effect::Writes), one, "writing promises nothing");
1097 for two in all {
1098 assert_eq!(one.and_then(two), two.and_then(one), "{one:?} and {two:?} disagree");
1099 assert_eq!(one.as_well_as(two), two.as_well_as(one), "{one:?} and {two:?}");
1100 let both = one.and_then(two);
1102 assert!(both.reads() >= one.reads());
1103 assert!(both.writes() >= one.writes());
1104 }
1105 }
1106 assert_eq!(Effect::Nothing.name(), "nothing");
1107 assert_eq!(Effect::Reads.name(), "reads");
1108 assert_eq!(Effect::Writes.name(), "writes");
1109 }
1110
1111 #[test]
1112 fn a_read_is_a_read_and_only_a_write_is_a_write() {
1113 assert!(!Effect::Nothing.reads());
1114 assert!(!Effect::Nothing.writes());
1115 assert!(Effect::Reads.reads());
1116 assert!(!Effect::Reads.writes());
1117 assert!(Effect::Writes.reads(), "a written byte is one the call could have looked at");
1118 assert!(Effect::Writes.writes());
1119 }
1120
1121 #[test]
1122 fn nothing_known_about_anything_is_a_thing_this_can_be() {
1123 let mut names = Interner::new();
1124 let summaries = Summaries::nothing();
1125 assert!(summaries.is_empty());
1126 assert_eq!(summaries.len(), 0);
1127 assert!(summaries.of(names.intern("f")).is_none());
1128 }
1129
1130 #[test]
1131 fn only_a_direct_call_has_a_summary_at_the_call_site() {
1132 let mut worked = Worked::out(&[("callee", 1, AttrSet::READNONE, None)]);
1133 let func = Worked::caller(&mut worked.names);
1134 let direct = func.1;
1135 assert!(worked.summaries.at(&func.0, direct).is_some_and(Summary::touches_nothing));
1136 assert!(worked.summaries.at(&func.0, func.2).is_none(), "through an address");
1137 assert!(worked.summaries.at(&func.0, func.3).is_none(), "not a call at all");
1138 }
1139
1140 impl Worked {
1141 fn caller(names: &mut Interner) -> (Func, super::Inst, super::Inst, super::Inst) {
1143 let mut func = Func::new(names.intern("caller"), Signature::new());
1144 let block = func.create_block();
1145 let mut build = Builder::new(&mut func, block);
1146 let signature = build.func().add_signature(Signature::new());
1147 let direct = build.call(names.intern("callee"), signature, &[]);
1148 let varargs = build.func().push_abis(&[]);
1149 let info =
1150 build.func().add_call(rucc_ir::CallInfo { callee: None, signature, varargs });
1151 let indirect = build.inst(
1152 InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
1153 &[],
1154 );
1155 let end = build.ret(&[]);
1156 (func, direct, indirect, end)
1157 }
1158 }
1159}