rucc_ir/facts.rs
1//! What is known about a pointer, which is what discharges a check.
2//!
3//! Section 6.2.3 of `spec/safe-memory/06-instrumentation.md` calls these facts rather than
4//! instructions on purpose. A check is code and costs something; a fact is a thing the optimizer
5//! may assume, established in one place and exploited in another, and costs nothing at all. That
6//! is the same shape `nsw` has, and it is what makes check elimination a dataflow problem instead
7//! of a pass with its own opinions.
8//!
9//! There are four of them and a value carries any combination, including none, which is what
10//! every value in a function compiled without `-fsafety` carries. They live in a side table on
11//! [`Func`](crate::Func) rather than in the value itself, so a module with no safety in it is the
12//! same module it was before this existed.
13
14use std::fmt;
15
16use crate::Value;
17
18/// What is known about one value.
19///
20/// All four fields absent is the default and means nothing is known, which is not the same as
21/// knowing the pointer is bad. A fact is only ever a promise, never a denial, so an optimizer
22/// that loses one makes the program slower and never makes it wrong.
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
24pub struct Facts {
25 /// The range the pointer is known to lie in, which is `!bounds(lo, ext)`.
26 pub bounds: Option<Bounds>,
27 /// How many bytes at the pointer are known initialized, which is `!init(n)`.
28 pub init: Option<u64>,
29 /// The alignment the pointer is known to have, in bytes, which is `!aligned(a)`.
30 pub align: Option<u32>,
31 /// Whether the storage the pointer points into is known live here, which is `!live`.
32 pub live: bool,
33}
34
35impl Facts {
36 /// Nothing known, which is what every value has until something establishes otherwise.
37 pub const NONE: Self = Self { bounds: None, init: None, align: None, live: false };
38
39 /// Whether nothing at all is known, which is when there is nothing to print.
40 #[must_use]
41 pub const fn is_empty(self) -> bool {
42 self.bounds.is_none() && self.init.is_none() && self.align.is_none() && !self.live
43 }
44}
45
46/// The range a pointer is known to lie in.
47///
48/// Two values and not two numbers, because the range of a heap allocation is not known until it
49/// is made. Where they are constants the optimizer folds them like any other constant, which is
50/// the case document 07 section 7.4 collapses into one comparison.
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub struct Bounds {
53 /// Where the range starts.
54 pub lo: Value,
55 /// How many bytes long it is.
56 pub ext: Value,
57}
58
59impl fmt::Display for Facts {
60 /// The list form the textual IR uses, `!live, !aligned(8)`, with the facts in a fixed order
61 /// and nothing at all when none of them is known.
62 ///
63 /// The two values a `!bounds` names are written by their raw index, which is the printer's
64 /// numbering only when the function was built in print order. [`Printer`](crate::Printer)
65 /// writes them itself for that reason and this is here for a diagnostic to use.
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 let mut first = true;
68 let mut sep = |f: &mut fmt::Formatter<'_>| {
69 let out = if first { Ok(()) } else { f.write_str(", ") };
70 first = false;
71 out
72 };
73 if let Some(bounds) = self.bounds {
74 sep(f)?;
75 write!(f, "!bounds(%{}, %{})", bounds.lo.raw(), bounds.ext.raw())?;
76 }
77 if self.live {
78 sep(f)?;
79 f.write_str("!live")?;
80 }
81 if let Some(n) = self.init {
82 sep(f)?;
83 write!(f, "!init({n})")?;
84 }
85 if let Some(align) = self.align {
86 sep(f)?;
87 write!(f, "!aligned({align})")?;
88 }
89 Ok(())
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn nothing_known_prints_as_nothing() {
99 assert!(Facts::NONE.is_empty());
100 assert_eq!(Facts::NONE.to_string(), "");
101 assert_eq!(Facts::default(), Facts::NONE);
102 }
103
104 #[test]
105 fn the_facts_print_in_the_order_the_specification_lists_them() {
106 let facts = Facts {
107 bounds: Some(Bounds { lo: Value::from_usize(3), ext: Value::from_usize(4) }),
108 init: Some(16),
109 align: Some(8),
110 live: true,
111 };
112 assert!(!facts.is_empty());
113 assert_eq!(facts.to_string(), "!bounds(%3, %4), !live, !init(16), !aligned(8)");
114 }
115
116 #[test]
117 fn one_fact_on_its_own_carries_no_separator() {
118 assert_eq!(Facts { live: true, ..Facts::NONE }.to_string(), "!live");
119 assert_eq!(Facts { align: Some(4), ..Facts::NONE }.to_string(), "!aligned(4)");
120 }
121}