Skip to main content

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}