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