1 //! Fix-point analyses on the IR using the "monotone framework".
2 //!
3 //! A lattice is a set with a partial ordering between elements, where there is
4 //! a single least upper bound and a single greatest least bound for every
5 //! subset. We are dealing with finite lattices, which means that it has a
6 //! finite number of elements, and it follows that there exists a single top and
7 //! a single bottom member of the lattice. For example, the power set of a
8 //! finite set forms a finite lattice where partial ordering is defined by set
9 //! inclusion, that is `a <= b` if `a` is a subset of `b`. Here is the finite
10 //! lattice constructed from the set {0,1,2}:
11 //!
12 //! ```text
13 //! .----- Top = {0,1,2} -----.
14 //! / | \
15 //! / | \
16 //! / | \
17 //! {0,1} -------. {0,2} .--------- {1,2}
18 //! | \ / \ / |
19 //! | / \ |
20 //! | / \ / \ |
21 //! {0} --------' {1} `---------- {2}
22 //! \ | /
23 //! \ | /
24 //! \ | /
25 //! `------ Bottom = {} ------'
26 //! ```
27 //!
28 //! A monotone function `f` is a function where if `x <= y`, then it holds that
29 //! `f(x) <= f(y)`. It should be clear that running a monotone function to a
30 //! fix-point on a finite lattice will always terminate: `f` can only "move"
31 //! along the lattice in a single direction, and therefore can only either find
32 //! a fix-point in the middle of the lattice or continue to the top or bottom
33 //! depending if it is ascending or descending the lattice respectively.
34 //!
35 //! For a deeper introduction to the general form of this kind of analysis, see
36 //! [Static Program Analysis by Anders Møller and Michael I. Schwartzbach][spa].
37 //!
38 //! [spa]: https://cs.au.dk/~amoeller/spa/spa.pdf
39
40 // Re-export individual analyses.
41 mod template_params;
42 pub use self::template_params::UsedTemplateParameters;
43 mod derive;
44 pub use self::derive::{as_cannot_derive_set, CannotDerive, DeriveTrait};
45 mod has_vtable;
46 pub use self::has_vtable::{HasVtable, HasVtableAnalysis, HasVtableResult};
47 mod has_destructor;
48 pub use self::has_destructor::HasDestructorAnalysis;
49 mod has_type_param_in_array;
50 pub use self::has_type_param_in_array::HasTypeParameterInArray;
51 mod has_float;
52 pub use self::has_float::HasFloat;
53 mod sizedness;
54 pub use self::sizedness::{Sizedness, SizednessAnalysis, SizednessResult};
55
56 use crate::ir::context::{BindgenContext, ItemId};
57
58 use crate::ir::traversal::{EdgeKind, Trace};
59 use crate::HashMap;
60 use std::fmt;
61 use std::ops;
62
63 /// An analysis in the monotone framework.
64 ///
65 /// Implementors of this trait must maintain the following two invariants:
66 ///
67 /// 1. The concrete data must be a member of a finite-height lattice.
68 /// 2. The concrete `constrain` method must be monotone: that is,
69 /// if `x <= y`, then `constrain(x) <= constrain(y)`.
70 ///
71 /// If these invariants do not hold, iteration to a fix-point might never
72 /// complete.
73 ///
74 /// For a simple example analysis, see the `ReachableFrom` type in the `tests`
75 /// module below.
76 pub trait MonotoneFramework: Sized + fmt::Debug {
77 /// The type of node in our dependency graph.
78 ///
79 /// This is just generic (and not `ItemId`) so that we can easily unit test
80 /// without constructing real `Item`s and their `ItemId`s.
81 type Node: Copy;
82
83 /// Any extra data that is needed during computation.
84 ///
85 /// Again, this is just generic (and not `&BindgenContext`) so that we can
86 /// easily unit test without constructing real `BindgenContext`s full of
87 /// real `Item`s and real `ItemId`s.
88 type Extra: Sized;
89
90 /// The final output of this analysis. Once we have reached a fix-point, we
91 /// convert `self` into this type, and return it as the final result of the
92 /// analysis.
93 type Output: From<Self> + fmt::Debug;
94
95 /// Construct a new instance of this analysis.
new(extra: Self::Extra) -> Self96 fn new(extra: Self::Extra) -> Self;
97
98 /// Get the initial set of nodes from which to start the analysis. Unless
99 /// you are sure of some domain-specific knowledge, this should be the
100 /// complete set of nodes.
initial_worklist(&self) -> Vec<Self::Node>101 fn initial_worklist(&self) -> Vec<Self::Node>;
102
103 /// Update the analysis for the given node.
104 ///
105 /// If this results in changing our internal state (ie, we discovered that
106 /// we have not reached a fix-point and iteration should continue), return
107 /// `ConstrainResult::Changed`. Otherwise, return `ConstrainResult::Same`.
108 /// When `constrain` returns `ConstrainResult::Same` for all nodes in the
109 /// set, we have reached a fix-point and the analysis is complete.
constrain(&mut self, node: Self::Node) -> ConstrainResult110 fn constrain(&mut self, node: Self::Node) -> ConstrainResult;
111
112 /// For each node `d` that depends on the given `node`'s current answer when
113 /// running `constrain(d)`, call `f(d)`. This informs us which new nodes to
114 /// queue up in the worklist when `constrain(node)` reports updated
115 /// information.
each_depending_on<F>(&self, node: Self::Node, f: F) where F: FnMut(Self::Node)116 fn each_depending_on<F>(&self, node: Self::Node, f: F)
117 where
118 F: FnMut(Self::Node);
119 }
120
121 /// Whether an analysis's `constrain` function modified the incremental results
122 /// or not.
123 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
124 pub enum ConstrainResult {
125 /// The incremental results were updated, and the fix-point computation
126 /// should continue.
127 Changed,
128
129 /// The incremental results were not updated.
130 Same,
131 }
132
133 impl Default for ConstrainResult {
default() -> Self134 fn default() -> Self {
135 ConstrainResult::Same
136 }
137 }
138
139 impl ops::BitOr for ConstrainResult {
140 type Output = Self;
141
bitor(self, rhs: ConstrainResult) -> Self::Output142 fn bitor(self, rhs: ConstrainResult) -> Self::Output {
143 if self == ConstrainResult::Changed || rhs == ConstrainResult::Changed {
144 ConstrainResult::Changed
145 } else {
146 ConstrainResult::Same
147 }
148 }
149 }
150
151 impl ops::BitOrAssign for ConstrainResult {
bitor_assign(&mut self, rhs: ConstrainResult)152 fn bitor_assign(&mut self, rhs: ConstrainResult) {
153 *self = *self | rhs;
154 }
155 }
156
157 /// Run an analysis in the monotone framework.
analyze<Analysis>(extra: Analysis::Extra) -> Analysis::Output where Analysis: MonotoneFramework,158 pub fn analyze<Analysis>(extra: Analysis::Extra) -> Analysis::Output
159 where
160 Analysis: MonotoneFramework,
161 {
162 let mut analysis = Analysis::new(extra);
163 let mut worklist = analysis.initial_worklist();
164
165 while let Some(node) = worklist.pop() {
166 if let ConstrainResult::Changed = analysis.constrain(node) {
167 analysis.each_depending_on(node, |needs_work| {
168 worklist.push(needs_work);
169 });
170 }
171 }
172
173 analysis.into()
174 }
175
176 /// Generate the dependency map for analysis
generate_dependencies<F>( ctx: &BindgenContext, consider_edge: F, ) -> HashMap<ItemId, Vec<ItemId>> where F: Fn(EdgeKind) -> bool,177 pub fn generate_dependencies<F>(
178 ctx: &BindgenContext,
179 consider_edge: F,
180 ) -> HashMap<ItemId, Vec<ItemId>>
181 where
182 F: Fn(EdgeKind) -> bool,
183 {
184 let mut dependencies = HashMap::default();
185
186 for &item in ctx.allowlisted_items() {
187 dependencies.entry(item).or_insert(vec![]);
188
189 {
190 // We reverse our natural IR graph edges to find dependencies
191 // between nodes.
192 item.trace(
193 ctx,
194 &mut |sub_item: ItemId, edge_kind| {
195 if ctx.allowlisted_items().contains(&sub_item) &&
196 consider_edge(edge_kind)
197 {
198 dependencies
199 .entry(sub_item)
200 .or_insert(vec![])
201 .push(item);
202 }
203 },
204 &(),
205 );
206 }
207 }
208 dependencies
209 }
210
211 #[cfg(test)]
212 mod tests {
213 use super::*;
214 use crate::{HashMap, HashSet};
215
216 // Here we find the set of nodes that are reachable from any given
217 // node. This is a lattice mapping nodes to subsets of all nodes. Our join
218 // function is set union.
219 //
220 // This is our test graph:
221 //
222 // +---+ +---+
223 // | | | |
224 // | 1 | .----| 2 |
225 // | | | | |
226 // +---+ | +---+
227 // | | ^
228 // | | |
229 // | +---+ '------'
230 // '----->| |
231 // | 3 |
232 // .------| |------.
233 // | +---+ |
234 // | ^ |
235 // v | v
236 // +---+ | +---+ +---+
237 // | | | | | | |
238 // | 4 | | | 5 |--->| 6 |
239 // | | | | | | |
240 // +---+ | +---+ +---+
241 // | | | |
242 // | | | v
243 // | +---+ | +---+
244 // | | | | | |
245 // '----->| 7 |<-----' | 8 |
246 // | | | |
247 // +---+ +---+
248 //
249 // And here is the mapping from a node to the set of nodes that are
250 // reachable from it within the test graph:
251 //
252 // 1: {3,4,5,6,7,8}
253 // 2: {2}
254 // 3: {3,4,5,6,7,8}
255 // 4: {3,4,5,6,7,8}
256 // 5: {3,4,5,6,7,8}
257 // 6: {8}
258 // 7: {3,4,5,6,7,8}
259 // 8: {}
260
261 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
262 struct Node(usize);
263
264 #[derive(Clone, Debug, Default, PartialEq, Eq)]
265 struct Graph(HashMap<Node, Vec<Node>>);
266
267 impl Graph {
make_test_graph() -> Graph268 fn make_test_graph() -> Graph {
269 let mut g = Graph::default();
270 g.0.insert(Node(1), vec![Node(3)]);
271 g.0.insert(Node(2), vec![Node(2)]);
272 g.0.insert(Node(3), vec![Node(4), Node(5)]);
273 g.0.insert(Node(4), vec![Node(7)]);
274 g.0.insert(Node(5), vec![Node(6), Node(7)]);
275 g.0.insert(Node(6), vec![Node(8)]);
276 g.0.insert(Node(7), vec![Node(3)]);
277 g.0.insert(Node(8), vec![]);
278 g
279 }
280
reverse(&self) -> Graph281 fn reverse(&self) -> Graph {
282 let mut reversed = Graph::default();
283 for (node, edges) in self.0.iter() {
284 reversed.0.entry(*node).or_insert(vec![]);
285 for referent in edges.iter() {
286 reversed.0.entry(*referent).or_insert(vec![]).push(*node);
287 }
288 }
289 reversed
290 }
291 }
292
293 #[derive(Clone, Debug, PartialEq, Eq)]
294 struct ReachableFrom<'a> {
295 reachable: HashMap<Node, HashSet<Node>>,
296 graph: &'a Graph,
297 reversed: Graph,
298 }
299
300 impl<'a> MonotoneFramework for ReachableFrom<'a> {
301 type Node = Node;
302 type Extra = &'a Graph;
303 type Output = HashMap<Node, HashSet<Node>>;
304
new(graph: &'a Graph) -> ReachableFrom305 fn new(graph: &'a Graph) -> ReachableFrom {
306 let reversed = graph.reverse();
307 ReachableFrom {
308 reachable: Default::default(),
309 graph: graph,
310 reversed: reversed,
311 }
312 }
313
initial_worklist(&self) -> Vec<Node>314 fn initial_worklist(&self) -> Vec<Node> {
315 self.graph.0.keys().cloned().collect()
316 }
317
constrain(&mut self, node: Node) -> ConstrainResult318 fn constrain(&mut self, node: Node) -> ConstrainResult {
319 // The set of nodes reachable from a node `x` is
320 //
321 // reachable(x) = s_0 U s_1 U ... U reachable(s_0) U reachable(s_1) U ...
322 //
323 // where there exist edges from `x` to each of `s_0, s_1, ...`.
324 //
325 // Yes, what follows is a **terribly** inefficient set union
326 // implementation. Don't copy this code outside of this test!
327
328 let original_size = self
329 .reachable
330 .entry(node)
331 .or_insert(HashSet::default())
332 .len();
333
334 for sub_node in self.graph.0[&node].iter() {
335 self.reachable.get_mut(&node).unwrap().insert(*sub_node);
336
337 let sub_reachable = self
338 .reachable
339 .entry(*sub_node)
340 .or_insert(HashSet::default())
341 .clone();
342
343 for transitive in sub_reachable {
344 self.reachable.get_mut(&node).unwrap().insert(transitive);
345 }
346 }
347
348 let new_size = self.reachable[&node].len();
349 if original_size != new_size {
350 ConstrainResult::Changed
351 } else {
352 ConstrainResult::Same
353 }
354 }
355
each_depending_on<F>(&self, node: Node, mut f: F) where F: FnMut(Node),356 fn each_depending_on<F>(&self, node: Node, mut f: F)
357 where
358 F: FnMut(Node),
359 {
360 for dep in self.reversed.0[&node].iter() {
361 f(*dep);
362 }
363 }
364 }
365
366 impl<'a> From<ReachableFrom<'a>> for HashMap<Node, HashSet<Node>> {
from(reachable: ReachableFrom<'a>) -> Self367 fn from(reachable: ReachableFrom<'a>) -> Self {
368 reachable.reachable
369 }
370 }
371
372 #[test]
monotone()373 fn monotone() {
374 let g = Graph::make_test_graph();
375 let reachable = analyze::<ReachableFrom>(&g);
376 println!("reachable = {:#?}", reachable);
377
378 fn nodes<A>(nodes: A) -> HashSet<Node>
379 where
380 A: AsRef<[usize]>,
381 {
382 nodes.as_ref().iter().cloned().map(Node).collect()
383 }
384
385 let mut expected = HashMap::default();
386 expected.insert(Node(1), nodes([3, 4, 5, 6, 7, 8]));
387 expected.insert(Node(2), nodes([2]));
388 expected.insert(Node(3), nodes([3, 4, 5, 6, 7, 8]));
389 expected.insert(Node(4), nodes([3, 4, 5, 6, 7, 8]));
390 expected.insert(Node(5), nodes([3, 4, 5, 6, 7, 8]));
391 expected.insert(Node(6), nodes([8]));
392 expected.insert(Node(7), nodes([3, 4, 5, 6, 7, 8]));
393 expected.insert(Node(8), nodes([]));
394 println!("expected = {:#?}", expected);
395
396 assert_eq!(reachable, expected);
397 }
398 }
399