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]
174 pub fn outside(&self) -> Effect {
175 self.outside
176 }
177
178 #[must_use]
183 pub fn param(&self, index: usize) -> Touch {
184 self.params.get(index).copied().unwrap_or_else(Touch::everything)
185 }
186
187 #[must_use]
189 pub fn arity(&self) -> usize {
190 self.params.len()
191 }
192
193 #[must_use]
199 pub fn only_through_arguments(&self) -> bool {
200 self.outside == Effect::Nothing
201 }
202
203 #[must_use]
205 pub fn writes_nothing(&self) -> bool {
206 !self.outside.writes() && self.params.iter().all(|touch| !touch.effect.writes())
207 }
208
209 #[must_use]
211 pub fn touches_nothing(&self) -> bool {
212 self.outside == Effect::Nothing
213 && self.params.iter().all(|touch| touch.effect == Effect::Nothing)
214 }
215
216 #[must_use]
218 fn as_well_as(&self, other: &Self) -> Self {
219 let arity = self.params.len().max(other.params.len());
220 let params = (0..arity).map(|at| self.param(at).as_well_as(other.param(at))).collect();
221 Self { outside: self.outside.as_well_as(other.outside), params }
222 }
223
224 fn touch_everything(&mut self) {
226 self.outside = Effect::Writes;
227 for touch in &mut self.params {
228 *touch = Touch::everything();
229 }
230 }
231}
232
233#[derive(Clone, Debug, Default)]
239pub struct Summaries {
240 known: HashMap<Symbol, Summary>,
241}
242
243impl Summaries {
244 #[must_use]
246 pub fn nothing() -> Self {
247 Self::default()
248 }
249
250 #[must_use]
258 pub fn of_module(module: &Module) -> Self {
259 let mut summaries = Self::default();
260 for id in module.funcs() {
261 let func = &module[id];
262 let arity = func.signature().params.len();
263 if let Some(summary) = from_attributes(func.attrs.set, arity) {
264 summaries.known.insert(func.name, summary);
265 }
266 }
267 summaries
268 }
269
270 #[must_use]
272 pub fn of(&self, name: Symbol) -> Option<&Summary> {
273 self.known.get(&name)
274 }
275
276 #[must_use]
281 pub fn at(&self, func: &Func, call: Inst) -> Option<&Summary> {
282 match Callee::of(func, call)? {
283 Callee::Direct(name) => self.of(name),
284 Callee::Indirect | Callee::Intrinsic(_) | Callee::Asm => None,
285 }
286 }
287
288 pub fn record(&mut self, name: Symbol, summary: Summary) {
294 let merged = match self.known.get(&name) {
295 Some(said) => said.as_well_as(&summary),
296 None => summary,
297 };
298 self.known.insert(name, merged);
299 }
300
301 #[must_use]
303 pub fn len(&self) -> usize {
304 self.known.len()
305 }
306
307 #[must_use]
309 pub fn is_empty(&self) -> bool {
310 self.known.is_empty()
311 }
312}
313
314fn from_attributes(set: AttrSet, arity: usize) -> Option<Summary> {
322 if set.contains(AttrSet::READNONE) {
323 let params = vec![Touch { effect: Effect::Nothing, escapes: true }; arity];
324 return Some(Summary { outside: Effect::Nothing, params: params.into() });
325 }
326 if set.contains(AttrSet::READONLY) {
330 let params = vec![Touch { effect: Effect::Reads, escapes: true }; arity];
331 return Some(Summary { outside: Effect::Reads, params: params.into() });
332 }
333 if set.contains(AttrSet::ARGMEM_ONLY) {
335 let params = vec![Touch::everything(); arity];
336 return Some(Summary { outside: Effect::Nothing, params: params.into() });
337 }
338 None
339}
340
341pub fn summarize(module: &Module, graph: &CallGraph, summaries: &mut Summaries) {
347 let arity = |node: Node| match graph.func(node) {
348 Some(id) => module[id].signature().params.len(),
349 None => 0,
350 };
351 let answers = graph.solve(
352 |node| Summary::nothing(arity(node)),
353 |node, answers| match graph.trusted_body(node) {
354 Some(id) => what_the_body_does(&module[id], graph, answers, summaries),
355 None => match summaries.of(graph.name(node)) {
358 Some(said) => said.clone(),
359 None => Summary::everything(arity(node)),
360 },
361 },
362 );
363 for node in graph.nodes() {
364 if graph.trusted_body(node).is_none() {
365 continue;
366 }
367 summaries.record(graph.name(node), answers[node.index()].clone());
368 }
369}
370
371fn what_the_body_does(
373 func: &Func,
374 graph: &CallGraph,
375 answers: &[Summary],
376 said: &Summaries,
377) -> Summary {
378 let arity = func.signature().params.len();
379 let Some(entry) = func.entry() else { return Summary::everything(arity) };
380 if func[entry].params.len() != arity {
384 return Summary::everything(arity);
385 }
386 let mut callees: HashMap<Inst, Summary> = HashMap::new();
391 let mut steps = 0;
392 for block in func.blocks() {
393 for inst in func.insts(block) {
394 steps += 1;
395 if steps > MAX_STEPS {
396 return Summary::everything(arity);
397 }
398 if let Some(summary) = what_that_call_does(func, inst, graph, answers, said) {
399 callees.insert(inst, summary);
400 }
401 }
402 }
403 let escapes = Escapes::with(func, |inst, index| {
404 callees.get(&inst).is_some_and(|summary| !summary.param(index).escapes)
405 });
406 let mut summary = Summary::nothing(arity);
407 for block in func.blocks() {
408 for inst in func.insts(block) {
409 add_block_escapes(func, entry, &mut summary, inst);
412 if let Some(callee) = callees.get(&inst) {
413 add_call(func, entry, &escapes, &mut summary, inst, callee);
414 continue;
415 }
416 add_access(func, entry, &escapes, &mut summary, inst);
417 add_operand_escapes(func, entry, &mut summary, inst);
418 }
419 }
420 summary
421}
422
423fn what_that_call_does(
425 func: &Func,
426 inst: Inst,
427 graph: &CallGraph,
428 answers: &[Summary],
429 said: &Summaries,
430) -> Option<Summary> {
431 let callee = Callee::of(func, inst)?;
432 let direct = matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall);
436 let Callee::Direct(name) = callee else {
437 return Some(Summary::everything(0));
439 };
440 if !direct {
441 return Some(Summary::everything(0));
442 }
443 let walked = graph.node(name).map(|node| answers[node.index()].clone());
447 let promised = said.of(name).cloned();
448 Some(match (walked, promised) {
449 (Some(walked), Some(promised)) => walked.as_well_as(&promised),
450 (Some(only), None) | (None, Some(only)) => only,
451 (None, None) => Summary::everything(0),
452 })
453}
454
455fn add_call(
457 func: &Func,
458 entry: Block,
459 escapes: &Escapes,
460 summary: &mut Summary,
461 inst: Inst,
462 callee: &Summary,
463) {
464 summary.outside = summary.outside.and_then(callee.outside());
465 let args = &func[func[inst].args];
466 for (at, &arg) in args.iter().enumerate() {
467 if !func[arg].ty.is_ptr() {
468 continue;
469 }
470 let touch = callee.param(at);
471 if touch == Touch::nothing() {
472 continue;
473 }
474 match behind(func, entry, escapes, arg) {
475 Behind::Param(at) => summary.params[at] = summary.params[at].and_then(touch),
480 Behind::Private => {}
483 Behind::Outside => summary.outside = summary.outside.and_then(touch.effect),
484 }
485 }
486}
487
488fn add_access(func: &Func, entry: Block, escapes: &Escapes, summary: &mut Summary, inst: Inst) {
490 let data = func[inst];
491 if !data.opcode.has_effects() || data.opcode.is_terminator() {
492 return;
493 }
494 if data.opcode.touches_only_planes() {
497 return;
498 }
499 let args = &func[data.args];
500 let mut through = |at: usize, effect: Effect| match behind(func, entry, escapes, args[at]) {
501 Behind::Param(at) => {
502 summary.params[at].effect = summary.params[at].effect.and_then(effect);
503 }
504 Behind::Private => {}
505 Behind::Outside => summary.outside = summary.outside.and_then(effect),
506 };
507 match data.opcode {
508 Opcode::Alloca => {}
510 Opcode::Load | Opcode::AtomicLoad | Opcode::Prefetch => through(0, Effect::Reads),
511 Opcode::Store | Opcode::AtomicStore => through(1, Effect::Writes),
512 Opcode::AtomicRmw | Opcode::Cmpxchg | Opcode::Memset => through(0, Effect::Writes),
513 Opcode::Memcpy | Opcode::Memmove => {
514 through(0, Effect::Writes);
515 through(1, Effect::Reads);
516 }
517 _ => summary.touch_everything(),
521 }
522}
523
524fn add_operand_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
531 let data = func[inst];
532 for (at, &arg) in func[data.args].iter().enumerate() {
533 if keeps_address(data.opcode, at) {
534 continue;
535 }
536 if let Some(at) = param_behind(func, entry, arg) {
537 summary.params[at].escapes = true;
538 }
539 }
540}
541
542fn add_block_escapes(func: &Func, entry: Block, summary: &mut Summary, inst: Inst) {
545 for call in func.successors(inst) {
546 for &arg in &func[call.args] {
547 if let Some(at) = param_behind(func, entry, arg) {
548 summary.params[at].escapes = true;
549 }
550 }
551 }
552}
553
554#[derive(Clone, Copy, Debug, PartialEq, Eq)]
556enum Behind {
557 Param(usize),
559 Private,
561 Outside,
563}
564
565fn behind(func: &Func, entry: Block, escapes: &Escapes, pointer: Value) -> Behind {
566 match origin(func, pointer).0 {
567 Origin::Local(local) if !escapes.escaped(local) => Behind::Private,
568 Origin::Unknown(value) => match param_of(func, entry, value) {
569 Some(at) => Behind::Param(at),
570 None => Behind::Outside,
571 },
572 _ => Behind::Outside,
573 }
574}
575
576fn param_behind(func: &Func, entry: Block, pointer: Value) -> Option<usize> {
578 let Origin::Unknown(value) = origin(func, pointer).0 else { return None };
579 param_of(func, entry, value)
580}
581
582fn param_of(func: &Func, entry: Block, value: Value) -> Option<usize> {
583 match func[value].def {
584 Def::Param { block, index } if block == entry => Some(index as usize),
585 _ => None,
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use rucc_base::Interner;
592 use rucc_ir::{
593 Builder, Extra, Flags, InstData, MemInfo, MemOrder, Pic, Restrict, Signature, Type,
594 };
595 use rucc_target::{TargetInfo, Triple};
596
597 use super::{
598 AttrSet, CallGraph, Effect, Func, Module, Opcode, Summaries, Summary, Touch, Value,
599 summarize,
600 };
601
602 fn access() -> MemInfo {
604 MemInfo {
605 size: 4,
606 align: 4,
607 owns: 4,
608 order: MemOrder::NotAtomic,
609 tbaa: None,
610 restrict: Restrict::NONE,
611 }
612 }
613
614 fn somewhere(build: &mut Builder<'_>, names: &mut Interner) -> Value {
616 let name = names.intern("v");
617 build.value(
618 InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
619 Type::PTR,
620 )
621 }
622
623 fn stack(build: &mut Builder<'_>) -> Value {
625 let mem = build.func().add_mem(access());
626 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
627 }
628
629 fn reads(build: &mut Builder<'_>, addr: Value) -> Value {
631 build.load(Type::int(32), addr, access(), Flags::NONE)
632 }
633
634 fn writes(build: &mut Builder<'_>, addr: Value) {
636 let zero = build.iconst(Type::int(32), 0);
637 build.store(zero, addr, access(), Flags::NONE);
638 }
639
640 fn calls(build: &mut Builder<'_>, names: &mut Interner, name: &str, args: &[Value]) {
642 let name = names.intern(name);
643 let params = vec![Type::PTR; args.len()];
644 let signature = build.func().add_signature(Signature::new().with_params(¶ms));
645 build.call(name, signature, args);
646 }
647
648 type Body = fn(&mut Interner, &mut Builder<'_>, &[Value]);
650
651 struct Worked {
653 names: Interner,
654 summaries: Summaries,
655 }
656
657 impl Worked {
658 fn out(bodies: &[(&str, usize, AttrSet, Option<Body>)]) -> Self {
660 let mut names = Interner::new();
661 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
662 let mut module = Module::new(names.intern("t.c"), &target);
663 for &(name, arity, attrs, body) in bodies {
664 let params = vec![Type::PTR; arity];
665 let mut func = Func::new(names.intern(name), Signature::new().with_params(¶ms));
666 func.attrs.set = attrs;
667 if let Some(body) = body {
668 let entry = func.create_block();
669 let args: Vec<Value> =
670 (0..arity).map(|_| func.append_param(entry, Type::PTR)).collect();
671 let mut build = Builder::new(&mut func, entry);
672 body(&mut names, &mut build, &args);
673 }
674 module.add_func(func);
675 }
676 let mut summaries = Summaries::of_module(&module);
677 summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
678 Self { names, summaries }
679 }
680
681 fn about(&mut self, name: &str) -> Summary {
683 let name = self.names.intern(name);
684 self.summaries.of(name).expect("a defined function has a summary").clone()
685 }
686 }
687
688 fn nothing(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
690 build.ret(&[]);
691 }
692
693 #[test]
694 fn a_body_that_goes_nowhere_near_memory_says_so() {
695 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(nothing))]);
696 let f = worked.about("f");
697 assert!(f.touches_nothing());
698 assert!(f.writes_nothing());
699 assert!(f.only_through_arguments());
700 assert_eq!(f.arity(), 2);
701 assert_eq!(f.param(0), Touch::nothing());
702 assert_eq!(f.param(1), Touch::nothing());
703 }
704
705 #[test]
706 fn a_load_through_one_parameter_is_a_read_of_that_one() {
707 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
708 let value = reads(build, args[0]);
709 build.ret(&[value]);
710 }
711 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
712 let f = worked.about("f");
713 assert_eq!(f.param(0).effect, Effect::Reads);
714 assert_eq!(f.param(1).effect, Effect::Nothing);
715 assert_eq!(f.outside(), Effect::Nothing);
716 assert!(f.writes_nothing());
717 assert!(f.only_through_arguments());
718 assert!(!f.param(0).escapes, "dereferencing an address is not keeping it");
719 }
720
721 #[test]
722 fn a_store_through_one_parameter_is_a_write_of_that_one() {
723 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
724 writes(build, args[1]);
725 build.ret(&[]);
726 }
727 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
728 let f = worked.about("f");
729 assert_eq!(f.param(0).effect, Effect::Nothing);
730 assert_eq!(f.param(1).effect, Effect::Writes);
731 assert!(!f.writes_nothing());
732 assert!(f.only_through_arguments(), "the only thing it wrote, it was handed");
733 }
734
735 #[test]
736 fn a_global_is_not_anybody_s_parameter() {
737 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
738 let global = somewhere(build, names);
739 writes(build, global);
740 build.ret(&[]);
741 }
742 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
743 let f = worked.about("f");
744 assert_eq!(f.outside(), Effect::Writes);
745 assert_eq!(f.param(0), Touch::nothing());
746 assert!(!f.only_through_arguments());
747 }
748
749 #[test]
750 fn what_a_function_did_to_its_own_stack_is_nobody_else_s_business() {
751 fn body(_: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
752 let local = stack(build);
753 writes(build, local);
754 let value = reads(build, local);
755 build.ret(&[value]);
756 }
757 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
758 assert!(worked.about("f").touches_nothing());
759 }
760
761 #[test]
762 fn a_copy_writes_the_one_it_writes_and_reads_the_one_it_reads() {
763 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
764 let mem = build.func().add_mem(access());
765 let list = build.func().push_values(&[args[0], args[1]]);
766 build.inst(
767 InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::Memcpy) },
768 &[],
769 );
770 build.ret(&[]);
771 }
772 let mut worked = Worked::out(&[("f", 2, AttrSet::NONE, Some(body))]);
773 let f = worked.about("f");
774 assert_eq!(f.param(0).effect, Effect::Writes);
775 assert_eq!(f.param(1).effect, Effect::Reads);
776 assert!(f.only_through_arguments());
777 assert!(!f.param(0).escapes);
778 assert!(!f.param(1).escapes);
779 }
780
781 #[test]
782 fn an_opcode_this_was_not_written_for_did_everything() {
783 fn body(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
786 let mem = build.func().add_mem(access());
787 let list = build.func().push_values(&[args[0]]);
788 build.inst(
789 InstData { args: list, extra: Extra::Mem(mem), ..InstData::new(Opcode::VaStart) },
790 &[],
791 );
792 build.ret(&[]);
793 }
794 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
795 let f = worked.about("f");
796 assert_eq!(f.outside(), Effect::Writes);
797 assert_eq!(f.param(0), Touch::everything());
798 }
799
800 #[test]
801 fn what_the_callee_does_to_what_it_was_handed_is_what_the_caller_does() {
802 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
803 writes(build, args[0]);
804 build.ret(&[]);
805 }
806 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
807 calls(build, names, "callee", &[args[1]]);
808 build.ret(&[]);
809 }
810 let mut worked = Worked::out(&[
811 ("callee", 1, AttrSet::NONE, Some(callee)),
812 ("caller", 2, AttrSet::NONE, Some(caller)),
813 ]);
814 let caller = worked.about("caller");
815 assert_eq!(caller.param(0), Touch::nothing());
818 assert_eq!(caller.param(1).effect, Effect::Writes);
819 assert_eq!(caller.outside(), Effect::Nothing);
820 assert!(caller.only_through_arguments());
821 }
822
823 #[test]
824 fn a_parameter_handed_to_something_that_does_not_keep_it_has_not_got_out() {
825 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
828 let value = reads(build, args[0]);
829 build.ret(&[value]);
830 }
831 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
832 calls(build, names, "callee", &[args[0]]);
833 build.ret(&[]);
834 }
835 let mut worked = Worked::out(&[
836 ("callee", 1, AttrSet::NONE, Some(callee)),
837 ("caller", 1, AttrSet::NONE, Some(caller)),
838 ]);
839 assert!(!worked.about("callee").param(0).escapes);
840 assert!(!worked.about("caller").param(0).escapes);
841 }
842
843 #[test]
844 fn a_parameter_written_down_somewhere_has_got_out() {
845 fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
846 let global = somewhere(build, names);
847 build.store(args[0], global, access(), Flags::NONE);
848 build.ret(&[]);
849 }
850 let mut worked = Worked::out(&[("f", 1, AttrSet::NONE, Some(body))]);
851 let f = worked.about("f");
852 assert!(f.param(0).escapes);
853 assert_eq!(f.param(0).effect, Effect::Nothing);
855 assert_eq!(f.outside(), Effect::Writes);
856 }
857
858 #[test]
859 fn a_parameter_a_caller_cannot_be_told_about_travels_up_as_a_write_of_everything() {
860 fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
861 let global = somewhere(build, names);
862 build.store(args[0], global, access(), Flags::NONE);
863 build.ret(&[]);
864 }
865 fn caller(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
866 calls(build, names, "callee", &[args[0]]);
867 build.ret(&[]);
868 }
869 let mut worked = Worked::out(&[
870 ("callee", 1, AttrSet::NONE, Some(callee)),
871 ("caller", 1, AttrSet::NONE, Some(caller)),
872 ]);
873 let caller = worked.about("caller");
874 assert!(caller.param(0).escapes, "the callee kept it, so the caller let it go");
875 assert_eq!(caller.outside(), Effect::Writes);
876 }
877
878 #[test]
879 fn what_a_callee_did_to_a_local_it_was_only_lent_stays_inside() {
880 fn callee(_: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
885 writes(build, args[0]);
886 build.ret(&[]);
887 }
888 fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
889 let place = stack(build);
890 calls(build, names, "callee", &[place]);
891 build.ret(&[]);
892 }
893 let mut worked = Worked::out(&[
894 ("callee", 1, AttrSet::NONE, Some(callee)),
895 ("caller", 0, AttrSet::NONE, Some(caller)),
896 ]);
897 assert_eq!(worked.about("callee").param(0).effect, Effect::Writes);
898 assert!(worked.about("caller").touches_nothing());
899 }
900
901 #[test]
902 fn a_local_the_callee_wrote_down_is_one_this_function_lost() {
903 fn callee(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
906 let global = somewhere(build, names);
907 build.store(args[0], global, access(), Flags::NONE);
908 build.ret(&[]);
909 }
910 fn caller(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
911 let place = stack(build);
912 calls(build, names, "callee", &[place]);
913 writes(build, place);
914 build.ret(&[]);
915 }
916 let mut worked = Worked::out(&[
917 ("callee", 1, AttrSet::NONE, Some(callee)),
918 ("caller", 0, AttrSet::NONE, Some(caller)),
919 ]);
920 assert!(worked.about("callee").param(0).escapes);
921 assert_eq!(worked.about("caller").outside(), Effect::Writes);
922 }
923
924 #[test]
925 fn a_call_through_an_address_did_everything_to_everything() {
926 fn body(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
927 calls(build, names, "unknown", &[args[0]]);
928 build.ret(&[]);
929 }
930 let mut worked = Worked::out(&[
931 ("unknown", 1, AttrSet::NONE, None),
932 ("f", 1, AttrSet::NONE, Some(body)),
933 ]);
934 let f = worked.about("f");
935 assert_eq!(f.outside(), Effect::Writes);
936 assert_eq!(f.param(0), Touch::everything());
937 }
938
939 #[test]
940 fn two_functions_that_call_each_other_and_touch_nothing_touch_nothing() {
941 fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
945 calls(build, names, "pong", &[args[0]]);
946 build.ret(&[]);
947 }
948 fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
949 calls(build, names, "ping", &[args[0]]);
950 build.ret(&[]);
951 }
952 let mut worked = Worked::out(&[
953 ("ping", 1, AttrSet::NONE, Some(ping)),
954 ("pong", 1, AttrSet::NONE, Some(pong)),
955 ]);
956 assert!(worked.about("ping").touches_nothing());
957 assert!(worked.about("pong").touches_nothing());
958 }
959
960 #[test]
961 fn a_write_inside_a_cycle_is_still_found() {
962 fn ping(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
963 calls(build, names, "pong", &[args[0]]);
964 build.ret(&[]);
965 }
966 fn pong(names: &mut Interner, build: &mut Builder<'_>, args: &[Value]) {
967 writes(build, args[0]);
968 calls(build, names, "ping", &[args[0]]);
969 build.ret(&[]);
970 }
971 let mut worked = Worked::out(&[
972 ("ping", 1, AttrSet::NONE, Some(ping)),
973 ("pong", 1, AttrSet::NONE, Some(pong)),
974 ]);
975 assert_eq!(worked.about("ping").param(0).effect, Effect::Writes);
976 assert_eq!(worked.about("pong").param(0).effect, Effect::Writes);
977 assert!(worked.about("ping").only_through_arguments());
978 }
979
980 #[test]
981 fn a_declaration_is_whatever_it_promised_and_nothing_more() {
982 let mut worked = Worked::out(&[
983 ("plain", 1, AttrSet::NONE, None),
984 ("none", 1, AttrSet::READNONE, None),
985 ("only", 1, AttrSet::READONLY, None),
986 ("args", 1, AttrSet::ARGMEM_ONLY, None),
987 ]);
988 let names = worked.names.intern("plain");
989 assert!(worked.summaries.of(names).is_none(), "nobody promised anything about it");
990 assert!(worked.about("none").touches_nothing());
991 let only = worked.about("only");
992 assert!(only.writes_nothing());
993 assert_eq!(only.outside(), Effect::Reads);
994 assert_eq!(only.param(0).effect, Effect::Reads);
995 let args = worked.about("args");
996 assert!(args.only_through_arguments());
997 assert!(!args.writes_nothing());
998 assert_eq!(args.param(0), Touch::everything());
999 }
1000
1001 #[test]
1002 fn no_attribute_promises_the_address_was_not_kept() {
1003 let mut worked = Worked::out(&[
1006 ("none", 1, AttrSet::READNONE, None),
1007 ("only", 1, AttrSet::READONLY, None),
1008 ("args", 1, AttrSet::ARGMEM_ONLY, None),
1009 ]);
1010 for name in ["none", "only", "args"] {
1011 assert!(worked.about(name).param(0).escapes, "{name} promised no such thing");
1012 }
1013 }
1014
1015 #[test]
1016 fn a_promise_the_body_does_not_keep_is_still_a_promise() {
1017 fn body(names: &mut Interner, build: &mut Builder<'_>, _: &[Value]) {
1021 let global = somewhere(build, names);
1022 writes(build, global);
1023 build.ret(&[]);
1024 }
1025 let mut worked = Worked::out(&[("f", 1, AttrSet::READNONE, Some(body))]);
1026 assert!(worked.about("f").touches_nothing());
1027 }
1028
1029 #[test]
1030 fn an_entry_block_that_does_not_match_the_signature_gets_no_answer() {
1031 let mut names = Interner::new();
1036 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1037 let mut module = Module::new(names.intern("t.c"), &target);
1038 let params = vec![Type::PTR; 2];
1039 let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
1040 let entry = func.create_block();
1041 let only = func.append_param(entry, Type::PTR);
1042 let mut build = Builder::new(&mut func, entry);
1043 build.ret(&[only]);
1044 module.add_func(func);
1045
1046 let mut summaries = Summaries::of_module(&module);
1047 summarize(&module, &CallGraph::of(&module, Pic::Executable), &mut summaries);
1048 let f = summaries.of(names.intern("f")).expect("a defined function has a summary");
1049 assert_eq!(*f, Summary::everything(2));
1050 }
1051
1052 #[test]
1053 fn a_position_no_parameter_stands_for_is_a_position_anything_happened_to() {
1054 let summary = Summary::nothing(1);
1057 assert_eq!(summary.param(0), Touch::nothing());
1058 assert_eq!(summary.param(1), Touch::everything());
1059 assert_eq!(summary.param(9), Touch::everything());
1060 }
1061
1062 #[test]
1063 fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
1064 let all = [Effect::Nothing, Effect::Reads, Effect::Writes];
1065 for one in all {
1066 assert_eq!(one.and_then(one), one, "{one:?} is not idempotent");
1067 assert_eq!(one.as_well_as(one), one, "{one:?} is not idempotent");
1068 assert_eq!(one.and_then(Effect::Nothing), one, "nothing happening changes nothing");
1069 assert_eq!(one.as_well_as(Effect::Writes), one, "writing promises nothing");
1070 for two in all {
1071 assert_eq!(one.and_then(two), two.and_then(one), "{one:?} and {two:?} disagree");
1072 assert_eq!(one.as_well_as(two), two.as_well_as(one), "{one:?} and {two:?}");
1073 let both = one.and_then(two);
1075 assert!(both.reads() >= one.reads());
1076 assert!(both.writes() >= one.writes());
1077 }
1078 }
1079 assert_eq!(Effect::Nothing.name(), "nothing");
1080 assert_eq!(Effect::Reads.name(), "reads");
1081 assert_eq!(Effect::Writes.name(), "writes");
1082 }
1083
1084 #[test]
1085 fn a_read_is_a_read_and_only_a_write_is_a_write() {
1086 assert!(!Effect::Nothing.reads());
1087 assert!(!Effect::Nothing.writes());
1088 assert!(Effect::Reads.reads());
1089 assert!(!Effect::Reads.writes());
1090 assert!(Effect::Writes.reads(), "a written byte is one the call could have looked at");
1091 assert!(Effect::Writes.writes());
1092 }
1093
1094 #[test]
1095 fn nothing_known_about_anything_is_a_thing_this_can_be() {
1096 let mut names = Interner::new();
1097 let summaries = Summaries::nothing();
1098 assert!(summaries.is_empty());
1099 assert_eq!(summaries.len(), 0);
1100 assert!(summaries.of(names.intern("f")).is_none());
1101 }
1102
1103 #[test]
1104 fn only_a_direct_call_has_a_summary_at_the_call_site() {
1105 let mut worked = Worked::out(&[("callee", 1, AttrSet::READNONE, None)]);
1106 let func = Worked::caller(&mut worked.names);
1107 let direct = func.1;
1108 assert!(worked.summaries.at(&func.0, direct).is_some_and(Summary::touches_nothing));
1109 assert!(worked.summaries.at(&func.0, func.2).is_none(), "through an address");
1110 assert!(worked.summaries.at(&func.0, func.3).is_none(), "not a call at all");
1111 }
1112
1113 impl Worked {
1114 fn caller(names: &mut Interner) -> (Func, super::Inst, super::Inst, super::Inst) {
1116 let mut func = Func::new(names.intern("caller"), Signature::new());
1117 let block = func.create_block();
1118 let mut build = Builder::new(&mut func, block);
1119 let signature = build.func().add_signature(Signature::new());
1120 let direct = build.call(names.intern("callee"), signature, &[]);
1121 let varargs = build.func().push_abis(&[]);
1122 let info =
1123 build.func().add_call(rucc_ir::CallInfo { callee: None, signature, varargs });
1124 let indirect = build.inst(
1125 InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
1126 &[],
1127 );
1128 let end = build.ret(&[]);
1129 (func, direct, indirect, end)
1130 }
1131 }
1132}