| 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(crate) use self::template_params::UsedTemplateParameters; |
| 43 | mod derive; |
| 44 | pub use self::derive::DeriveTrait; |
| 45 | pub(crate) use self::derive::{as_cannot_derive_set, CannotDerive}; |
| 46 | mod has_vtable; |
| 47 | pub(crate) use self::has_vtable::{ |
| 48 | HasVtable, HasVtableAnalysis, HasVtableResult, |
| 49 | }; |
| 50 | mod has_destructor; |
| 51 | pub(crate) use self::has_destructor::HasDestructorAnalysis; |
| 52 | mod has_type_param_in_array; |
| 53 | pub(crate) use self::has_type_param_in_array::HasTypeParameterInArray; |
| 54 | mod has_float; |
| 55 | pub(crate) use self::has_float::HasFloat; |
| 56 | mod sizedness; |
| 57 | pub(crate) use self::sizedness::{ |
| 58 | Sizedness, SizednessAnalysis, SizednessResult, |
| 59 | }; |
| 60 | |
| 61 | use crate::ir::context::{BindgenContext, ItemId}; |
| 62 | |
| 63 | use crate::ir::traversal::{EdgeKind, Trace}; |
| 64 | use crate::HashMap; |
| 65 | use std::fmt; |
| 66 | use 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. |
| 81 | pub(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, Default)] |
| 129 | pub(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 | #[default] |
| 136 | Same, |
| 137 | } |
| 138 | |
| 139 | impl ops::BitOr for ConstrainResult { |
| 140 | type Output = Self; |
| 141 | |
| 142 | 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 { |
| 152 | fn bitor_assign(&mut self, rhs: ConstrainResult) { |
| 153 | *self = *self | rhs; |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | /// Run an analysis in the monotone framework. |
| 158 | pub(crate) fn analyze<Analysis>(extra: Analysis::Extra) -> Analysis::Output |
| 159 | where |
| 160 | Analysis: MonotoneFramework, |
| 161 | { |
| 162 | let mut analysis: Analysis = Analysis::new(extra); |
| 163 | let mut worklist: Vec<::Node> = analysis.initial_worklist(); |
| 164 | |
| 165 | while let Some(node: ::Node) = worklist.pop() { |
| 166 | if let ConstrainResult::Changed = analysis.constrain(node) { |
| 167 | analysis.each_depending_on(node, |needs_work: ::Node| { |
| 168 | worklist.push(needs_work); |
| 169 | }); |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | analysis.into() |
| 174 | } |
| 175 | |
| 176 | /// Generate the dependency map for analysis |
| 177 | pub(crate) 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_with(Vec::new); |
| 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_with(Vec::new) |
| 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::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 { |
| 268 | 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 | |
| 281 | 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_with(Vec::new); |
| 285 | for referent in edges.iter() { |
| 286 | reversed |
| 287 | .0 |
| 288 | .entry(*referent) |
| 289 | .or_insert_with(Vec::new) |
| 290 | .push(*node); |
| 291 | } |
| 292 | } |
| 293 | reversed |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | #[derive (Clone, Debug, PartialEq, Eq)] |
| 298 | struct ReachableFrom<'a> { |
| 299 | reachable: HashMap<Node, HashSet<Node>>, |
| 300 | graph: &'a Graph, |
| 301 | reversed: Graph, |
| 302 | } |
| 303 | |
| 304 | impl<'a> MonotoneFramework for ReachableFrom<'a> { |
| 305 | type Node = Node; |
| 306 | type Extra = &'a Graph; |
| 307 | type Output = HashMap<Node, HashSet<Node>>; |
| 308 | |
| 309 | fn new(graph: &'a Graph) -> ReachableFrom { |
| 310 | let reversed = graph.reverse(); |
| 311 | ReachableFrom { |
| 312 | reachable: Default::default(), |
| 313 | graph, |
| 314 | reversed, |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | fn initial_worklist(&self) -> Vec<Node> { |
| 319 | self.graph.0.keys().cloned().collect() |
| 320 | } |
| 321 | |
| 322 | fn constrain(&mut self, node: Node) -> ConstrainResult { |
| 323 | // The set of nodes reachable from a node `x` is |
| 324 | // |
| 325 | // reachable(x) = s_0 U s_1 U ... U reachable(s_0) U reachable(s_1) U ... |
| 326 | // |
| 327 | // where there exist edges from `x` to each of `s_0, s_1, ...`. |
| 328 | // |
| 329 | // Yes, what follows is a **terribly** inefficient set union |
| 330 | // implementation. Don't copy this code outside of this test! |
| 331 | |
| 332 | let original_size = self.reachable.entry(node).or_default().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 = |
| 338 | self.reachable.entry(*sub_node).or_default().clone(); |
| 339 | |
| 340 | for transitive in sub_reachable { |
| 341 | self.reachable.get_mut(&node).unwrap().insert(transitive); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | let new_size = self.reachable[&node].len(); |
| 346 | if original_size != new_size { |
| 347 | ConstrainResult::Changed |
| 348 | } else { |
| 349 | ConstrainResult::Same |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | fn each_depending_on<F>(&self, node: Node, mut f: F) |
| 354 | where |
| 355 | F: FnMut(Node), |
| 356 | { |
| 357 | for dep in self.reversed.0[&node].iter() { |
| 358 | f(*dep); |
| 359 | } |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | impl<'a> From<ReachableFrom<'a>> for HashMap<Node, HashSet<Node>> { |
| 364 | fn from(reachable: ReachableFrom<'a>) -> Self { |
| 365 | reachable.reachable |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | #[test ] |
| 370 | fn monotone() { |
| 371 | let g = Graph::make_test_graph(); |
| 372 | let reachable = analyze::<ReachableFrom>(&g); |
| 373 | println!("reachable = {:#?}" , reachable); |
| 374 | |
| 375 | fn nodes<A>(nodes: A) -> HashSet<Node> |
| 376 | where |
| 377 | A: AsRef<[usize]>, |
| 378 | { |
| 379 | nodes.as_ref().iter().cloned().map(Node).collect() |
| 380 | } |
| 381 | |
| 382 | let mut expected = HashMap::default(); |
| 383 | expected.insert(Node(1), nodes([3, 4, 5, 6, 7, 8])); |
| 384 | expected.insert(Node(2), nodes([2])); |
| 385 | expected.insert(Node(3), nodes([3, 4, 5, 6, 7, 8])); |
| 386 | expected.insert(Node(4), nodes([3, 4, 5, 6, 7, 8])); |
| 387 | expected.insert(Node(5), nodes([3, 4, 5, 6, 7, 8])); |
| 388 | expected.insert(Node(6), nodes([8])); |
| 389 | expected.insert(Node(7), nodes([3, 4, 5, 6, 7, 8])); |
| 390 | expected.insert(Node(8), nodes([])); |
| 391 | println!("expected = {:#?}" , expected); |
| 392 | |
| 393 | assert_eq!(reachable, expected); |
| 394 | } |
| 395 | } |
| 396 | |