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}