Skip to main content

rucc_opt/
outside.rs

1//! The handful of module facts a pass handed one function cannot reach, copied out once.
2//!
3//! `crate::alias` is written, tested and documented, and until this existed nothing in the pipeline
4//! asked it anything. The reason was structural rather than anybody forgetting. A pass is handed
5//! one function, `Alias::new` wanted the module the function is in, and the manager hands out
6//! `&mut module[id]`, so a pass could not borrow the module even to ask a question about it. That
7//! is tamnd/rucc#1467.
8//!
9//! What the oracle wants from the module is four things and none of them is large.
10//!
11//! What each name refers to, so that two symbols are known to be two objects. An `alias` or an
12//! `ifunc` is exactly a second name for something, so the question is not whether the module has
13//! the name but which of the three kinds it is.
14//!
15//! What a function a call names is declared to do to memory, which is `const`, `pure` and
16//! `argmemonly` as the C programmer wrote them.
17//!
18//! The parent of each type node, which is the whole of what the type based layer walks. The nodes
19//! are a flat table on the module and the walk only ever goes up, so a vector of parents indexed
20//! the way the table is is the same walk with the module left behind.
21//!
22//! The data layout, which is two words and is there because the width of a pointer is the target's
23//! answer rather than the type's.
24//!
25//! # Why a copy and not a borrow
26//!
27//! The same argument `crate::image` makes, and the pipeline does the same thing with the result: one
28//! of these is built for the module before any pass runs and each function's analysis cache holds a
29//! counted reference to it. A pass then builds its oracle out of the function it was handed and the
30//! cache it was handed, and borrows nothing that is being mutated.
31//!
32//! The size is the reason this is affordable where a borrow would have been free. A symbol table
33//! entry is a word and a tag, a parent is an index, and neither is per instruction. The images this
34//! sits beside are a copy of the module's read only data and are built anyway.
35//!
36//! # The empty one
37//!
38//! [`Outside::default`] knows nothing, and an oracle built on it answers `May` to everything it
39//! would have used the module for. That is what a caller with no module gets, and it is the
40//! conservative direction, so a test or a tool that builds a cache without a pipeline is slower
41//! rather than wrong.
42
43use std::collections::HashMap;
44
45use rucc_base::Symbol;
46use rucc_ir::{Attrs, DataLayout, Meta, Module};
47
48/// What one name in the module refers to, as much of it as the oracle asks about.
49#[derive(Clone, Copy, Debug)]
50enum Named {
51    /// A function, and what it is declared to do to memory.
52    Func(Attrs),
53    /// A global variable.
54    Global,
55    /// An alias or an ifunc, which is a second name for an object that has another one.
56    Second,
57}
58
59/// The module, as much of it as an alias oracle asks about.
60#[derive(Clone, Debug, Default)]
61pub struct Outside {
62    /// What each name the module defines or declares refers to.
63    names: HashMap<Symbol, Named>,
64    /// The node one level up from each metadata node, indexed the way the module's table is.
65    parents: Vec<Option<Meta>>,
66    /// What the module was built assuming, or nothing when this was built without a module.
67    layout: Option<DataLayout>,
68}
69
70impl Outside {
71    /// Copies the four facts out of a module.
72    #[must_use]
73    pub fn of(module: &Module) -> Self {
74        let mut names = HashMap::new();
75        for id in module.funcs() {
76            names.insert(module[id].name, Named::Func(module[id].attrs));
77        }
78        for id in module.globals() {
79            names.insert(module[id].name, Named::Global);
80        }
81        for id in module.aliases() {
82            names.insert(module[id].name, Named::Second);
83        }
84        // In the order the module has them, so a node's own index is where its parent sits here.
85        let parents = module.metadata().map(|node| module[node].parent()).collect();
86        Self { names, parents, layout: Some(module.datalayout) }
87    }
88
89    /// Whether this symbol is a name for an object no other name in the module also names.
90    ///
91    /// A name the module does not have at all is treated the way an alias is, because something is
92    /// wrong and the conservative answer is the one to be wrong in the direction of.
93    #[must_use]
94    pub fn one_object(&self, name: Symbol) -> bool {
95        matches!(self.names.get(&name), Some(Named::Func(_) | Named::Global))
96    }
97
98    /// What a function of that name is declared to do to memory.
99    ///
100    /// Nothing for a name that is not a function here, which covers an indirect call, a call of
101    /// something declared in another module and a call through an alias.
102    #[must_use]
103    pub fn attrs(&self, name: Symbol) -> Option<Attrs> {
104        match self.names.get(&name)? {
105            Named::Func(attrs) => Some(*attrs),
106            Named::Global | Named::Second => None,
107        }
108    }
109
110    /// The type node one level up from this one, or nothing at the root.
111    #[must_use]
112    pub fn parent(&self, node: Meta) -> Option<Meta> {
113        self.parents.get(node.index()).copied().flatten()
114    }
115
116    /// How many bytes an address takes on the target this module was built for.
117    #[must_use]
118    pub fn pointer_bytes(&self) -> Option<u64> {
119        Some(u64::from(self.layout?.pointer_bits).div_ceil(8))
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use rucc_base::Interner;
126    use rucc_ir::{Alias, AttrSet, Attrs, Func, Global, MetaNode, Module, Signature, TbaaNode};
127    use rucc_target::{TargetInfo, Triple};
128
129    use super::Outside;
130
131    /// An empty module for a sixty four bit Linux.
132    fn module(names: &mut Interner) -> Module {
133        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
134        Module::new(names.intern("t.c"), &target)
135    }
136
137    #[test]
138    fn a_function_and_a_global_each_name_one_object() {
139        let mut names = Interner::new();
140        let mut module = module(&mut names);
141        let f = names.intern("f");
142        let table = names.intern("table");
143        module.add_func(Func::new(f, Signature::new()));
144        module.add_global(Global::new(table, 16, 8));
145        let outside = Outside::of(&module);
146        assert!(outside.one_object(f));
147        assert!(outside.one_object(table));
148    }
149
150    #[test]
151    fn an_alias_and_a_name_nobody_has_do_not() {
152        // Two names for one object is the case the rule that two objects do not alias must not
153        // reach, and a name this module never saw is the same answer for a different reason.
154        let mut names = Interner::new();
155        let mut module = module(&mut names);
156        let here = names.intern("here");
157        let second = names.intern("second");
158        module.add_func(Func::new(here, Signature::new()));
159        module.add_alias(Alias::new(second, here));
160        let outside = Outside::of(&module);
161        assert!(!outside.one_object(second));
162        assert!(!outside.one_object(names.intern("nowhere")));
163    }
164
165    #[test]
166    fn a_function_carries_what_it_is_declared_to_do_to_memory() {
167        let mut names = Interner::new();
168        let mut module = module(&mut names);
169        let f = names.intern("f");
170        let mut func = Func::new(f, Signature::new());
171        func.attrs = Attrs { set: AttrSet::READONLY, ..Attrs::default() };
172        module.add_func(func);
173        let table = names.intern("table");
174        module.add_global(Global::new(table, 16, 8));
175        let outside = Outside::of(&module);
176        assert!(outside.attrs(f).expect("it is a function").set.contains(AttrSet::READONLY));
177        assert!(outside.attrs(table).is_none(), "a global is not something a call names");
178    }
179
180    #[test]
181    fn a_type_node_knows_the_one_above_it() {
182        let mut names = Interner::new();
183        let mut module = module(&mut names);
184        let root = module.add_meta(MetaNode::Tbaa(TbaaNode {
185            name: names.intern("omnipotent char"),
186            parent: None,
187            offset: 0,
188        }));
189        let under = module.add_meta(MetaNode::Tbaa(TbaaNode {
190            name: names.intern("int"),
191            parent: Some(root),
192            offset: 0,
193        }));
194        let outside = Outside::of(&module);
195        assert_eq!(outside.parent(under), Some(root));
196        assert_eq!(outside.parent(root), None);
197    }
198
199    #[test]
200    fn the_empty_one_knows_nothing_and_says_so() {
201        let mut names = Interner::new();
202        let outside = Outside::default();
203        assert!(!outside.one_object(names.intern("f")));
204        assert!(outside.attrs(names.intern("f")).is_none());
205        assert!(outside.pointer_bytes().is_none());
206    }
207
208    #[test]
209    fn a_module_says_how_wide_an_address_is() {
210        let mut names = Interner::new();
211        let module = module(&mut names);
212        assert_eq!(Outside::of(&module).pointer_bytes(), Some(8));
213    }
214}