1use std::collections::HashMap;
42
43use rucc_base::Symbol;
44use rucc_ir::{AttrSet, Block, Def, Extra, Flags, Func, Inst, MemOrder, 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 if data.flags.contains(Flags::VOLATILE)
532 || matches!(data.extra, Extra::Mem(mem) if func[mem].order != MemOrder::NotAtomic)
533 {
534 summary.touch_everything();
535 return;
536 }
537 let args = &func[data.args];
538 let mut through = |at: usize, effect: Effect| match behind(func, entry, escapes, args[at]) {
539 Behind::Param(at) => {
540 summary.params[at].effect = summary.params[at].effect.and_then(effect);
541 }
542 Behind::Private => {}
543 Behind::Outside => summary.outside = summary.outside.and_then(effect),
544 };
545 match data.opcode {
546 Opcode::Alloca => {}
548 Opcode::Load | Opcode::AtomicLoad | Opcode::Prefetch => through(0, Effect::Reads),
549 Opcode::Store | Opcode::AtomicStore => through(1, Effect::Writes),
550 Opcode::AtomicRmw | Opcode::Cmpxchg | Opcode::Memset => through(0, Effect::Writes),
551 Opcode::Memcpy | Opcode::Memmove => {
552 through(0, Effect::Writes);
553 through(1, Effect::Reads);
554 }
555 _ => summary.touch_everything(),
559 }
560}
561
562fn add_operand_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
569 let data = func[inst];
570 for (at, &arg) in func[data.args].iter().enumerate() {
571 if keeps_address(data.opcode, at) {
572 continue;
573 }
574 if let Some(at) = param_behind(func, entry, arg) {
575 summary.params[at].escapes = true;
576 }
577 }
578}
579
580fn add_block_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
583 for call in func.successors(inst) {
584 for &arg in &func[call.args] {
585 if let Some(at) = param_behind(func, entry, arg) {
586 summary.params[at].escapes = true;
587 }
588 }
589 }
590}
591
592#[derive(Clone, Copy, Debug, PartialEq, Eq)]
594enum Behind {
595 Param(usize),
597 Private,
599 Outside,
601}
602
603fn behind(func: &Func, entry: Block, escapes: &Escapes, pointer: Value) -> Behind {
604 match origin(func, pointer).0 {
605 Origin::Local(local) if !escapes.escaped(local) => Behind::Private,
606 Origin::Unknown(value) => match param_of(func, entry, value) {
607 Some(at) => Behind::Param(at),
608 None => Behind::Outside,
609 },
610 _ => Behind::Outside,
611 }
612}
613
614fn param_behind(func: &Func, entry: Block, pointer: Value) -> Option<usize> {
616 let Origin::Unknown(value) = origin(func, pointer).0 else { return None };
617 param_of(func, entry, value)
618}
619
620fn param_of(func: &Func, entry: Block, value: Value) -> Option<usize> {
621 match func[value].def {
622 Def::Param { block, index } if block == entry => Some(index as usize),
623 _ => None,
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use rucc_base::Interner;
630 use rucc_ir::{
631 Builder, Extra, Flags, InstData, MemInfo, MemOrder, Pic, Restrict, Signature, Type,
632 };
633 use rucc_target::{TargetInfo, Triple};
634
635 use super::{
636 AttrSet, CallGraph, Effect, Func, Module, Opcode, Summaries, Summary, Touch, Value,
637 summarize,
638 };
639
640 fn access() -> MemInfo {
642 MemInfo {
643 size: 4,
644 align: 4,
645 owns: 4,
646 order: MemOrder::NotAtomic,
647 tbaa: None,
648 restrict: Restrict::NONE,
649 }
650 }
651
652 fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
654 let name = names.intern("v");
655 build.value(
656 InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
657 Type::PTR,
658 )
659 }
660
661 fn stack(build: &mut Builder<'_>) -> Value {
663 let mem = build.func().add_mem(access());
664 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
665 }
666
667 fn reads(build: &mut Builder<'_>, addr: Value) -> Value {
669 build.load(Type::int(32), addr, access(), Flags::NONE)
670 }
671
672 fn writes(build: &mut Builder<'_>, addr: Value) {
674 let zero = build.iconst(Type::int(32), 0);
675 build.store(zero, addr, access(), Flags::NONE);
676 }
677
678 fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str, args: &[Value]) {
680 let name = names.intern(name);
681 let params = vec![Type::PTR; args.len()];
682 let signature = build.func().add_signature(Signature::new().with_params(¶ms));
683 build.call(name, signature, args);
684 }
685
686 type Body = fn(&mut Interner, &mut Builder<'_>, &[Value]);
688
689 struct Worked {
691 names: Interner,
692 summaries: Summaries,
693 }
694
695 impl Worked {
696 fn out(bodies: &[(&str, usize, AttrSet, Option<Body>)]) -> Self {
698 let mut names = Interner::new();
699 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
700 let mut module = Module::new(names.intern("t.c"), &target);
701 for &(name, arity, attrs, body) in bodies {
702 let params = vec![Type::PTR; arity];
703 let mut func = Func::new(names.intern(name), Signature::new().with_params(¶ms));
704 func.attrs.set = attrs;
705 if let Some(body) = body {
706 let entry = func.create_block();
707 let args: Vec<Value> =
708 (0..arity).map(|_| func.append_param(entry, Type::PTR)).collect();
709 let mut build = Builder::new(&mut func, entry);
710 body(&mut names, &mut build, &args);
711 }
712 module.add_func(func);
713 }
714 let mut summaries = Summaries::of_module(&module);
715 summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
716 Self { names, summaries }
717 }
718
719 fn about(&mut self, name: &str) -> Summary {
721 let name = self.names.intern(name);
722 self.summaries.of(name).expect("a defined function has a summary").clone()
723 }
724 }
725
726 fn nothing(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
728 build.ret(&[]);
729 }
730
731 #[test]
732 fn a_body_that_goes_nowhere_near_memory_says_so() {
733 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(nothing))]);
734 let f = worked.about("f");
735 assert!(f.touches_nothing());
736 assert!(f.writes_nothing());
737 assert!(f.only_through_arguments());
738 assert_eq!(f.arity(), 2);
739 assert_eq!(f.param(0), Touch::nothing());
740 assert_eq!(f.param(1), Touch::nothing());
741 }
742
743 #[test]
744 fn a_load_through_one_parameter_is_a_read_of_that_one() {
745 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
746 let value = reads(build, args[0]);
747 build.ret(&[value]);
748 }
749 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
750 let f = worked.about("f");
751 assert_eq!(f.param(0).effect, Effect::Reads);
752 assert_eq!(f.param(1).effect, Effect::Nothing);
753 assert_eq!(f.outside(), Effect::Nothing);
754 assert!(f.writes_nothing());
755 assert!(f.only_through_arguments());
756 assert!(!f.param(0).escapes, "dereferencing an address is not keeping it");
757 }
758
759 #[test]
762 fn a_volatile_load_is_everything() {
763 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
764 let global = somewhere(build, names);
765 let value = build.load(Type::int(32), global, access(), Flags::VOLATILE);
766 build.ret(&[value]);
767 }
768 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
769 let f = worked.about("f");
770 assert_eq!(f.outside(), Effect::Writes);
771 assert!(!f.writes_nothing());
772 assert_eq!(f.param(0), Touch::everything());
773 }
774
775 #[test]
778 fn an_atomic_load_is_everything() {
779 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
780 let global = somewhere(build, names);
781 let order = MemInfo { order: MemOrder::Acquire, ..access() };
782 let value = build.atomic_load(Type::int(32), global, order, Flags::NONE);
783 build.ret(&[value]);
784 }
785 let mut worked = Worked::out(&[("f", 0, AttrSet::NONE, Some(body))]);
786 let f = worked.about("f");
787 assert_eq!(f.outside(), Effect::Writes);
788 assert!(!f.writes_nothing());
789 }
790
791 #[test]
792 fn a_store_through_one_parameter_is_a_write_of_that_one() {
793 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
794 writes(build, args[1]);
795 build.ret(&[]);
796 }
797 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
798 let f = worked.about("f");
799 assert_eq!(f.param(0).effect, Effect::Nothing);
800 assert_eq!(f.param(1).effect, Effect::Writes);
801 assert!(!f.writes_nothing());
802 assert!(f.only_through_arguments(), "the only thing it wrote, it was handed");
803 }
804
805 #[test]
806 fn a_global_is_not_anybody_s_parameter() {
807 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
808 let global = somewhere(build, names);
809 writes(build, global);
810 build.ret(&[]);
811 }
812 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
813 let f = worked.about("f");
814 assert_eq!(f.outside(), Effect::Writes);
815 assert_eq!(f.param(0), Touch::nothing());
816 assert!(!f.only_through_arguments());
817 }
818
819 #[test]
820 fn what_a_function_did_to_its_own_stack_is_nobody_else_s_business() {
821 fn body(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
822 let local = stack(build);
823 writes(build, local);
824 let value = reads(build, local);
825 build.ret(&[value]);
826 }
827 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
828 assert!(worked.about("f").touches_nothing());
829 }
830
831 #[test]
832 fn a_copy_writes_the_one_it_writes_and_reads_the_one_it_reads() {
833 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
834 let mem = build.func().add_mem(access());
835 let list = build.func().push_values(&[args[0], args[1]]);
836 build.inst(
837 InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::Memcpy) },
838 &[],
839 );
840 build.ret(&[]);
841 }
842 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
843 let f = worked.about("f");
844 assert_eq!(f.param(0).effect, Effect::Writes);
845 assert_eq!(f.param(1).effect, Effect::Reads);
846 assert!(f.only_through_arguments());
847 assert!(!f.param(0).escapes);
848 assert!(!f.param(1).escapes);
849 }
850
851 #[test]
852 fn an_opcode_this_was_not_written_for_did_everything() {
853 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
856 let mem = build.func().add_mem(access());
857 let list = build.func().push_values(&[args[0]]);
858 build.inst(
859 InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::VaStart) },
860 &[],
861 );
862 build.ret(&[]);
863 }
864 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
865 let f = worked.about("f");
866 assert_eq!(f.outside(), Effect::Writes);
867 assert_eq!(f.param(0), Touch::everything());
868 }
869
870 #[test]
871 fn what_the_callee_does_to_what_it_was_handed_is_what_the_caller_does() {
872 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
873 writes(build, args[0]);
874 build.ret(&[]);
875 }
876 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
877 calls(build, names, "callee", &[args[1]]);
878 build.ret(&[]);
879 }
880 let mut worked = Worked::out(&[
881 ("callee", 1, AttrSet::NONE, Some(callee)),
882 ("caller", 2, AttrSet::NONE, Some(caller)),
883 ]);
884 let caller = worked.about("caller");
885 assert_eq!(caller.param(0), Touch::nothing());
888 assert_eq!(caller.param(1).effect, Effect::Writes);
889 assert_eq!(caller.outside(), Effect::Nothing);
890 assert!(caller.only_through_arguments());
891 }
892
893 #[test]
894 fn a_parameter_handed_to_something_that_does_not_keep_it_has_not_got_out() {
895 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
898 let value = reads(build, args[0]);
899 build.ret(&[value]);
900 }
901 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
902 calls(build, names, "callee", &[args[0]]);
903 build.ret(&[]);
904 }
905 let mut worked = Worked::out(&[
906 ("callee", 1, AttrSet::NONE, Some(callee)),
907 ("caller", 1, AttrSet::NONE, Some(caller)),
908 ]);
909 assert!(!worked.about("callee").param(0).escapes);
910 assert!(!worked.about("caller").param(0).escapes);
911 }
912
913 #[test]
914 fn a_parameter_written_down_somewhere_has_got_out() {
915 fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
916 let global = somewhere(build, names);
917 build.store(args[0], global, access(), Flags::NONE);
918 build.ret(&[]);
919 }
920 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
921 let f = worked.about("f");
922 assert!(f.param(0).escapes);
923 assert_eq!(f.param(0).effect, Effect::Nothing);
925 assert_eq!(f.outside(), Effect::Writes);
926 }
927
928 #[test]
929 fn a_parameter_a_caller_cannot_be_told_about_travels_up_as_a_write_of_everything() {
930 fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
931 let global = somewhere(build, names);
932 build.store(args[0], global, access(), Flags::NONE);
933 build.ret(&[]);
934 }
935 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
936 calls(build, names, "callee", &[args[0]]);
937 build.ret(&[]);
938 }
939 let mut worked = Worked::out(&[
940 ("callee", 1, AttrSet::NONE, Some(callee)),
941 ("caller", 1, AttrSet::NONE, Some(caller)),
942 ]);
943 let caller = worked.about("caller");
944 assert!(caller.param(0).escapes, "the callee kept it, so the caller let it go");
945 assert_eq!(caller.outside(), Effect::Writes);
946 }
947
948 #[test]
949 fn what_a_callee_did_to_a_local_it_was_only_lent_stays_inside() {
950 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
955 writes(build, args[0]);
956 build.ret(&[]);
957 }
958 fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
959 let place = stack(build);
960 calls(build, names, "callee", &[place]);
961 build.ret(&[]);
962 }
963 let mut worked = Worked::out(&[
964 ("callee", 1, AttrSet::NONE, Some(callee)),
965 ("caller", 0, AttrSet::NONE, Some(caller)),
966 ]);
967 assert_eq!(worked.about("callee").param(0).effect, Effect::Writes);
968 assert!(worked.about("caller").touches_nothing());
969 }
970
971 #[test]
972 fn a_local_the_callee_wrote_down_is_one_this_function_lost() {
973 fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
976 let global = somewhere(build, names);
977 build.store(args[0], global, access(), Flags::NONE);
978 build.ret(&[]);
979 }
980 fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
981 let place = stack(build);
982 calls(build, names, "callee", &[place]);
983 writes(build, place);
984 build.ret(&[]);
985 }
986 let mut worked = Worked::out(&[
987 ("callee", 1, AttrSet::NONE, Some(callee)),
988 ("caller", 0, AttrSet::NONE, Some(caller)),
989 ]);
990 assert!(worked.about("callee").param(0).escapes);
991 assert_eq!(worked.about("caller").outside(), Effect::Writes);
992 }
993
994 #[test]
995 fn a_call_through_an_address_did_everything_to_everything() {
996 fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
997 calls(build, names, "unknown", &[args[0]]);
998 build.ret(&[]);
999 }
1000 let mut worked = Worked::out(&[
1001 ("unknown", 1, AttrSet::NONE, None),
1002 ("f", 1, AttrSet::NONE, Some(body)),
1003 ]);
1004 let f = worked.about("f");
1005 assert_eq!(f.outside(), Effect::Writes);
1006 assert_eq!(f.param(0), Touch::everything());
1007 }
1008
1009 #[test]
1010 fn two_functions_that_call_each_other_and_touch_nothing_touch_nothing() {
1011 fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1015 calls(build, names, "pong", &[args[0]]);
1016 build.ret(&[]);
1017 }
1018 fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1019 calls(build, names, "ping", &[args[0]]);
1020 build.ret(&[]);
1021 }
1022 let mut worked = Worked::out(&[
1023 ("ping", 1, AttrSet::NONE, Some(ping)),
1024 ("pong", 1, AttrSet::NONE, Some(pong)),
1025 ]);
1026 assert!(worked.about("ping").touches_nothing());
1027 assert!(worked.about("pong").touches_nothing());
1028 }
1029
1030 #[test]
1031 fn a_write_inside_a_cycle_is_still_found() {
1032 fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1033 calls(build, names, "pong", &[args[0]]);
1034 build.ret(&[]);
1035 }
1036 fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
1037 writes(build, args[0]);
1038 calls(build, names, "ping", &[args[0]]);
1039 build.ret(&[]);
1040 }
1041 let mut worked = Worked::out(&[
1042 ("ping", 1, AttrSet::NONE, Some(ping)),
1043 ("pong", 1, AttrSet::NONE, Some(pong)),
1044 ]);
1045 assert_eq!(worked.about("ping").param(0).effect, Effect::Writes);
1046 assert_eq!(worked.about("pong").param(0).effect, Effect::Writes);
1047 assert!(worked.about("ping").only_through_arguments());
1048 }
1049
1050 #[test]
1051 fn a_declaration_is_whatever_it_promised_and_nothing_more() {
1052 let mut worked = Worked::out(&[
1053 ("plain", 1, AttrSet::NONE, None),
1054 ("none", 1, AttrSet::READNONE, None),
1055 ("only", 1, AttrSet::READONLY, None),
1056 ("args", 1, AttrSet::ARGMEM_ONLY, None),
1057 ]);
1058 let names = worked.names.intern("plain");
1059 assert!(worked.summaries.of(names).is_none(), "nobody promised anything about it");
1060 assert!(worked.about("none").touches_nothing());
1061 let only = worked.about("only");
1062 assert!(only.writes_nothing());
1063 assert_eq!(only.outside(), Effect::Reads);
1064 assert_eq!(only.param(0).effect, Effect::Reads);
1065 let args = worked.about("args");
1066 assert!(args.only_through_arguments());
1067 assert!(!args.writes_nothing());
1068 assert_eq!(args.param(0), Touch::everything());
1069 }
1070
1071 #[test]
1072 fn no_attribute_promises_the_address_was_not_kept() {
1073 let mut worked = Worked::out(&[
1076 ("none", 1, AttrSet::READNONE, None),
1077 ("only", 1, AttrSet::READONLY, None),
1078 ("args", 1, AttrSet::ARGMEM_ONLY, None),
1079 ]);
1080 for name in ["none", "only", "args"] {
1081 assert!(worked.about(name).param(0).escapes, "{name} promised no such thing");
1082 }
1083 }
1084
1085 #[test]
1086 fn a_promise_the_body_does_not_keep_is_still_a_promise() {
1087 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
1091 let global = somewhere(build, names);
1092 writes(build, global);
1093 build.ret(&[]);
1094 }
1095 let mut worked = Worked::out(&[("f", 1, AttrSet::READNONE, Some(body))]);
1096 assert!(worked.about("f").touches_nothing());
1097 }
1098
1099 #[test]
1100 fn an_entry_block_that_does_not_match_the_signature_gets_no_answer() {
1101 let mut names = Interner::new();
1106 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1107 let mut module = Module::new(names.intern("t.c"), &target);
1108 let params = vec![Type::PTR; 2];
1109 let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
1110 let entry = func.create_block();
1111 let only = func.append_param(entry, Type::PTR);
1112 let mut build = Builder::new(&mut func, entry);
1113 build.ret(&[only]);
1114 module.add_func(func);
1115
1116 let mut summaries = Summaries::of_module(&module);
1117 summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
1118 let f = summaries.of(names.intern("f")).expect("a defined function has a summary");
1119 assert_eq!(*f, Summary::everything(2));
1120 }
1121
1122 #[test]
1123 fn a_position_no_parameter_stands_for_is_a_position_anything_happened_to() {
1124 let summary = Summary::nothing(1);
1127 assert_eq!(summary.param(0), Touch::nothing());
1128 assert_eq!(summary.param(1), Touch::everything());
1129 assert_eq!(summary.param(9), Touch::everything());
1130 }
1131
1132 #[test]
1133 fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
1134 let all = [Effect::Nothing, Effect::Reads, Effect::Writes];
1135 for one in all {
1136 assert_eq!(one.and_then(one), one, "{one:?} is not idempotent");
1137 assert_eq!(one.as_well_as(one), one, "{one:?} is not idempotent");
1138 assert_eq!(one.and_then(Effect::Nothing), one, "nothing happening changes nothing");
1139 assert_eq!(one.as_well_as(Effect::Writes), one, "writing promises nothing");
1140 for two in all {
1141 assert_eq!(one.and_then(two), two.and_then(one), "{one:?} and {two:?} disagree");
1142 assert_eq!(one.as_well_as(two), two.as_well_as(one), "{one:?} and {two:?}");
1143 let both = one.and_then(two);
1145 assert!(both.reads() >= one.reads());
1146 assert!(both.writes() >= one.writes());
1147 }
1148 }
1149 assert_eq!(Effect::Nothing.name(), "nothing");
1150 assert_eq!(Effect::Reads.name(), "reads");
1151 assert_eq!(Effect::Writes.name(), "writes");
1152 }
1153
1154 #[test]
1155 fn a_read_is_a_read_and_only_a_write_is_a_write() {
1156 assert!(!Effect::Nothing.reads());
1157 assert!(!Effect::Nothing.writes());
1158 assert!(Effect::Reads.reads());
1159 assert!(!Effect::Reads.writes());
1160 assert!(Effect::Writes.reads(), "a written byte is one the call could have looked at");
1161 assert!(Effect::Writes.writes());
1162 }
1163
1164 #[test]
1165 fn nothing_known_about_anything_is_a_thing_this_can_be() {
1166 let mut names = Interner::new();
1167 let summaries = Summaries::nothing();
1168 assert!(summaries.is_empty());
1169 assert_eq!(summaries.len(), 0);
1170 assert!(summaries.of(names.intern("f")).is_none());
1171 }
1172
1173 #[test]
1174 fn only_a_direct_call_has_a_summary_at_the_call_site() {
1175 let mut worked = Worked::out(&[("callee", 1, AttrSet::READNONE, None)]);
1176 let func = Worked::caller(&mut worked.names);
1177 let direct = func.1;
1178 assert!(worked.summaries.at(&func.0, direct).is_some_and(Summary::touches_nothing));
1179 assert!(worked.summaries.at(&func.0, func.2).is_none(), "through an address");
1180 assert!(worked.summaries.at(&func.0, func.3).is_none(), "not a call at all");
1181 }
1182
1183 impl Worked {
1184 fn caller(names: &mut Interner) -> (Func, super::Inst, super::Inst, super::Inst) {
1186 let mut func = Func::new(names.intern("caller"), Signature::new());
1187 let block = func.create_block();
1188 let mut build = Builder::new(&mut func, block);
1189 let signature = build.func().add_signature(Signature::new());
1190 let direct = build.call(names.intern("callee"), signature, &[]);
1191 let varargs = build.func().push_abis(&[]);
1192 let info =
1193 build.func().add_call(rucc_ir::CallInfo { callee: None, signature, varargs });
1194 let indirect = build.inst(
1195 InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
1196 &[],
1197 );
1198 let end = build.ret(&[]);
1199 (func, direct, indirect, end)
1200 }
1201 }
1202}