1use std::collections::{HashMap, HashSet};
38
39use rucc_base::{Interner, Symbol};
40use rucc_ir::{AttrSet, Extra, Func, Inst, Module, Opcode};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum Purity {
49 Const,
51 LoopingConst,
54 Pure,
56 LoopingPure,
58 Opaque,
60}
61
62impl Purity {
63 pub const ALL: [Self; 5] =
65 [Self::Const, Self::LoopingConst, Self::Pure, Self::LoopingPure, Self::Opaque];
66
67 #[must_use]
69 pub const fn as_str(self) -> &'static str {
70 match self {
71 Self::Const => "const",
72 Self::LoopingConst => "const, may not return",
73 Self::Pure => "pure",
74 Self::LoopingPure => "pure, may not return",
75 Self::Opaque => "opaque",
76 }
77 }
78
79 #[must_use]
81 pub const fn reads_memory(self) -> bool {
82 match self {
83 Self::Const | Self::LoopingConst => false,
84 Self::Pure | Self::LoopingPure | Self::Opaque => true,
85 }
86 }
87
88 #[must_use]
93 pub const fn writes_memory(self) -> bool {
94 matches!(self, Self::Opaque)
95 }
96
97 #[must_use]
103 pub const fn terminates(self) -> bool {
104 matches!(self, Self::Const | Self::Pure)
105 }
106
107 #[must_use]
114 pub const fn depends_only_on_arguments(self) -> bool {
115 !self.reads_memory() && !self.writes_memory()
116 }
117
118 #[must_use]
124 pub const fn can_be_deleted_when_unused(self) -> bool {
125 !self.writes_memory() && self.terminates()
126 }
127
128 #[must_use]
134 pub const fn stronger(self, other: Self) -> Self {
135 match (self, other) {
136 (Self::Opaque, it) | (it, Self::Opaque) => it,
137 (one, two) => Self::of(
138 one.reads_memory() && two.reads_memory(),
139 one.terminates() || two.terminates(),
140 ),
141 }
142 }
143
144 #[must_use]
149 pub const fn weaker(self, other: Self) -> Self {
150 match (self, other) {
151 (Self::Opaque, _) | (_, Self::Opaque) => Self::Opaque,
152 (one, two) => Self::of(
153 one.reads_memory() || two.reads_memory(),
154 one.terminates() && two.terminates(),
155 ),
156 }
157 }
158
159 const fn of(reads: bool, terminates: bool) -> Self {
161 match (reads, terminates) {
162 (false, true) => Self::Const,
163 (false, false) => Self::LoopingConst,
164 (true, true) => Self::Pure,
165 (true, false) => Self::LoopingPure,
166 }
167 }
168}
169
170impl std::fmt::Display for Purity {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.write_str(self.as_str())
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
181pub enum Callee {
182 Direct(Symbol),
184 Indirect,
186 Intrinsic(Symbol),
189 Asm,
191}
192
193impl Callee {
194 #[must_use]
196 pub fn of(func: &Func, inst: Inst) -> Option<Self> {
197 let data = &func[inst];
198 match data.opcode {
199 Opcode::Call | Opcode::TailCall | Opcode::CallIndirect => match data.extra {
200 Extra::Call(at) => Some(match func[at].callee {
201 Some(name) => Self::Direct(name),
202 None => Self::Indirect,
203 }),
204 _ => Some(Self::Indirect),
205 },
206 Opcode::TargetIntrinsic => match data.extra {
207 Extra::Symbol(name) => Some(Self::Intrinsic(name)),
208 _ => Some(Self::Asm),
209 },
210 Opcode::InlineAsm => Some(Self::Asm),
211 _ => None,
212 }
213 }
214}
215
216#[derive(Debug, Clone, Default)]
222pub struct Facts {
223 declared: HashMap<Symbol, AttrSet>,
224 inferred: HashMap<Symbol, Purity>,
225 from_the_library: HashMap<Symbol, Purity>,
226}
227
228impl Facts {
229 #[must_use]
231 pub fn nothing() -> Self {
232 Self::default()
233 }
234
235 #[must_use]
240 pub fn of_module(module: &Module, names: &Interner) -> Self {
241 let mut facts = Self::default();
242 let mut defined = HashSet::new();
243 for id in module.funcs() {
244 let func = &module[id];
245 facts.declared.insert(func.name, func.attrs.set);
246 if !func.is_declaration() {
247 defined.insert(func.name);
248 }
249 }
250 for &name in facts.declared.keys() {
253 if defined.contains(&name) {
254 continue;
255 }
256 if let Some(purity) = library_purity(names.resolve(name)) {
257 facts.from_the_library.insert(name, purity);
258 }
259 }
260 facts
261 }
262
263 pub fn without_the_library(&mut self) {
268 self.from_the_library.clear();
269 }
270
271 pub fn not_the_library_name(&mut self, name: Symbol) {
276 self.from_the_library.remove(&name);
277 }
278
279 pub fn record_inferred(&mut self, name: Symbol, purity: Purity) {
285 self.inferred.insert(name, purity);
286 }
287
288 #[must_use]
290 pub fn declared(&self, name: Symbol) -> Purity {
291 match self.declared.get(&name) {
292 Some(&set) => from_attributes(set),
293 None => Purity::Opaque,
294 }
295 }
296
297 #[must_use]
299 pub fn inferred(&self, name: Symbol) -> Purity {
300 self.inferred.get(&name).copied().unwrap_or(Purity::Opaque)
301 }
302
303 #[must_use]
307 pub fn purity_of(&self, callee: Callee) -> Purity {
308 match callee {
309 Callee::Direct(name) => self.of_name(name),
310 Callee::Indirect => Purity::Opaque,
313 Callee::Intrinsic(_) => Purity::Opaque,
316 Callee::Asm => Purity::Opaque,
318 }
319 }
320
321 fn of_name(&self, name: Symbol) -> Purity {
323 let mut purity = self.declared(name).stronger(self.inferred(name));
324 if let Some(&known) = self.from_the_library.get(&name) {
325 purity = purity.stronger(known);
326 }
327 purity
328 }
329}
330
331fn from_attributes(set: AttrSet) -> Purity {
337 let terminates = !set.contains(AttrSet::NORETURN);
338 if set.contains(AttrSet::READNONE) {
339 return Purity::of(false, terminates);
340 }
341 if set.contains(AttrSet::READONLY) {
342 return Purity::of(true, terminates);
343 }
344 Purity::Opaque
345}
346
347const LIBRARY: &[(&str, Purity)] = &[
356 ("abs", Purity::Const),
357 ("imaxabs", Purity::Const),
358 ("labs", Purity::Const),
359 ("llabs", Purity::Const),
360 ("memchr", Purity::Pure),
361 ("memcmp", Purity::Pure),
362 ("strchr", Purity::Pure),
363 ("strcmp", Purity::Pure),
364 ("strcspn", Purity::Pure),
365 ("strlen", Purity::Pure),
366 ("strncmp", Purity::Pure),
367 ("strnlen", Purity::Pure),
368 ("strpbrk", Purity::Pure),
369 ("strrchr", Purity::Pure),
370 ("strspn", Purity::Pure),
371 ("strstr", Purity::Pure),
372];
373
374fn library_purity(name: &str) -> Option<Purity> {
379 let name = name.strip_prefix("__builtin_").unwrap_or(name);
380 LIBRARY.binary_search_by_key(&name, |&(named, _)| named).ok().map(|at| LIBRARY[at].1)
381}
382
383#[cfg(test)]
384mod tests {
385 use rucc_base::Interner;
386 use rucc_ir::{
387 AsmInfo, AttrSet, BlockCallList, Builder, CallInfo, Extra, Flags, Func, InstData, Module,
388 Opcode, Signature, Type,
389 };
390 use rucc_target::{TargetInfo, Triple};
391
392 use super::{Callee, Facts, LIBRARY, Purity};
393
394 fn module(named: &[(&str, bool, AttrSet)]) -> (Interner, Module) {
396 let mut names = Interner::new();
397 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
398 let mut module = Module::new(names.intern("t.c"), &target);
399 for &(name, defined, attrs) in named {
400 let mut func = Func::new(names.intern(name), Signature::new());
401 func.attrs.set = attrs;
402 if defined {
403 let block = func.create_block();
404 let mut build = Builder::new(&mut func, block);
405 let zero = build.iconst(Type::int(32), 0);
406 build.ret(&[zero]);
407 }
408 module.add_func(func);
409 }
410 (names, module)
411 }
412
413 fn purity(names: &mut Interner, module: &Module, name: &str) -> Purity {
415 let facts = Facts::of_module(module, names);
416 let symbol = names.intern(name);
417 facts.purity_of(Callee::Direct(symbol))
418 }
419
420 #[test]
421 fn a_function_nobody_promised_anything_about_is_opaque() {
422 let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
423 assert_eq!(purity(&mut names, &module, "f"), Purity::Opaque);
424 }
425
426 #[test]
427 fn a_name_this_module_never_heard_of_is_opaque_as_well() {
428 let (mut names, module) = module(&[("f", true, AttrSet::NONE)]);
429 let facts = Facts::of_module(&module, &names);
430 assert_eq!(facts.purity_of(Callee::Direct(names.intern("g"))), Purity::Opaque);
431 }
432
433 #[test]
434 fn the_const_attribute_is_honoured_because_the_user_asserted_it() {
435 let (mut names, module) = module(&[("f", false, AttrSet::READNONE)]);
436 let purity = purity(&mut names, &module, "f");
437 assert_eq!(purity, Purity::Const);
438 assert!(purity.depends_only_on_arguments());
439 assert!(purity.can_be_deleted_when_unused());
440 }
441
442 #[test]
443 fn the_pure_attribute_reads_memory_and_writes_none() {
444 let (mut names, module) = module(&[("f", false, AttrSet::READONLY)]);
445 let purity = purity(&mut names, &module, "f");
446 assert_eq!(purity, Purity::Pure);
447 assert!(purity.reads_memory());
448 assert!(!purity.writes_memory());
449 assert!(!purity.depends_only_on_arguments());
450 assert!(purity.can_be_deleted_when_unused());
451 }
452
453 #[test]
454 fn a_const_function_that_does_not_come_back_may_not_be_deleted() {
455 let (mut names, module) =
458 module(&[("f", false, AttrSet::READNONE.union(AttrSet::NORETURN))]);
459 let purity = purity(&mut names, &module, "f");
460 assert_eq!(purity, Purity::LoopingConst);
461 assert!(purity.depends_only_on_arguments());
462 assert!(!purity.can_be_deleted_when_unused());
463 }
464
465 #[test]
466 fn nothing_that_is_not_a_direct_call_is_anything_but_opaque() {
467 let (mut names, module) = module(&[("f", true, AttrSet::READNONE)]);
468 let facts = Facts::of_module(&module, &names);
469 assert_eq!(facts.purity_of(Callee::Indirect), Purity::Opaque);
472 assert_eq!(facts.purity_of(Callee::Asm), Purity::Opaque);
473 let vector = names.intern("__builtin_ia32_paddb");
474 assert_eq!(facts.purity_of(Callee::Intrinsic(vector)), Purity::Opaque);
475 }
476
477 #[test]
478 fn the_library_names_are_known_under_both_spellings() {
479 let (mut names, module) = module(&[
480 ("strlen", false, AttrSet::NONE),
481 ("abs", false, AttrSet::NONE),
482 ("__builtin_strlen", false, AttrSet::NONE),
483 ("printf", false, AttrSet::NONE),
484 ]);
485 assert_eq!(purity(&mut names, &module, "strlen"), Purity::Pure);
486 assert_eq!(purity(&mut names, &module, "__builtin_strlen"), Purity::Pure);
487 assert_eq!(purity(&mut names, &module, "abs"), Purity::Const);
488 assert_eq!(purity(&mut names, &module, "printf"), Purity::Opaque);
490 }
491
492 #[test]
493 fn a_module_that_defines_strlen_means_its_own() {
494 let (mut names, module) = module(&[("strlen", true, AttrSet::NONE)]);
495 assert_eq!(purity(&mut names, &module, "strlen"), Purity::Opaque);
496 }
497
498 #[test]
499 fn no_builtin_takes_the_table_away_and_the_named_form_takes_one_entry() {
500 let (mut names, module) =
501 module(&[("strlen", false, AttrSet::NONE), ("abs", false, AttrSet::NONE)]);
502 let mut facts = Facts::of_module(&module, &names);
503 let strlen = names.intern("strlen");
504 let abs = names.intern("abs");
505 facts.not_the_library_name(strlen);
506 assert_eq!(facts.purity_of(Callee::Direct(strlen)), Purity::Opaque);
507 assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Const);
508 facts.without_the_library();
509 assert_eq!(facts.purity_of(Callee::Direct(abs)), Purity::Opaque);
510 }
511
512 #[test]
513 fn what_the_user_wrote_and_what_analysis_worked_out_are_kept_apart() {
514 let (mut names, module) =
515 module(&[("f", true, AttrSet::READNONE.union(AttrSet::NORETURN))]);
516 let mut facts = Facts::of_module(&module, &names);
517 let f = names.intern("f");
518 assert_eq!(facts.declared(f), Purity::LoopingConst);
519 assert_eq!(facts.inferred(f), Purity::Opaque);
520 facts.record_inferred(f, Purity::Pure);
524 assert_eq!(facts.declared(f), Purity::LoopingConst);
525 assert_eq!(facts.inferred(f), Purity::Pure);
526 assert_eq!(facts.purity_of(Callee::Direct(f)), Purity::Const);
527 }
528
529 #[test]
530 fn what_an_instruction_calls_is_read_off_the_instruction() {
531 let mut names = Interner::new();
532 let mut func = Func::new(names.intern("caller"), Signature::new());
533 let block = func.create_block();
534 let mut build = Builder::new(&mut func, block);
535 let signature = build.func().add_signature(Signature::new());
536 let direct = build.call(names.intern("f"), signature, &[]);
537 let varargs = build.func().push_abis(&[]);
538 let info = build.func().add_call(CallInfo { callee: None, signature, varargs });
539 let indirect = build.inst(
540 InstData { extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
541 &[],
542 );
543 let asm = build.inline_asm(
544 AsmInfo {
545 template: names.intern("nop"),
546 constraints: names.intern(""),
547 clobbers: names.intern(""),
548 targets: BlockCallList::EMPTY,
549 },
550 &[],
551 &[],
552 Flags::NONE,
553 );
554 let nothing = build.ret(&[]);
555
556 let f = names.intern("f");
557 assert_eq!(Callee::of(&func, nothing), None);
558 assert_eq!(Callee::of(&func, direct), Some(Callee::Direct(f)));
559 assert_eq!(Callee::of(&func, indirect), Some(Callee::Indirect));
560 assert_eq!(Callee::of(&func, asm), Some(Callee::Asm));
561 }
562
563 #[test]
564 fn the_two_ways_of_combining_are_the_lattice_they_claim_to_be() {
565 for one in Purity::ALL {
566 assert_eq!(one.stronger(one), one, "{one} is not idempotent");
567 assert_eq!(one.weaker(one), one, "{one} is not idempotent");
568 assert_eq!(one.stronger(Purity::Opaque), one, "opaque should say nothing");
569 assert_eq!(one.weaker(Purity::Opaque), Purity::Opaque, "opaque covers everything");
570 for two in Purity::ALL {
571 assert_eq!(one.stronger(two), two.stronger(one), "{one} and {two} disagree");
572 assert_eq!(one.weaker(two), two.weaker(one), "{one} and {two} disagree");
573 let both = one.weaker(two);
575 assert!(both.reads_memory() >= one.reads_memory());
576 assert!(both.writes_memory() >= one.writes_memory());
577 assert!(both.terminates() <= one.terminates());
578 }
579 }
580 }
581
582 #[test]
583 fn only_an_opaque_call_may_write_memory() {
584 for purity in Purity::ALL {
585 assert_eq!(purity.writes_memory(), purity == Purity::Opaque, "{purity}");
586 assert_eq!(purity.can_be_deleted_when_unused(), purity.terminates(), "{purity}");
587 }
588 }
589
590 #[test]
591 fn the_library_table_is_sorted_says_each_name_once_and_writes_no_memory() {
592 for pair in LIBRARY.windows(2) {
595 assert!(pair[0].0 < pair[1].0, "{} and {} are out of order", pair[0].0, pair[1].0);
596 }
597 for &(name, purity) in LIBRARY {
598 assert!(!purity.writes_memory(), "{name} would not be worth an entry");
599 assert!(purity.terminates(), "{name} is in the table to be deletable");
600 assert!(!name.starts_with("__builtin_"), "{name} is reached under both spellings");
601 }
602 }
603}