|
| 1 | +use std::fmt; |
| 2 | + |
| 3 | +/// An analysis in the monotone framework. |
| 4 | +/// |
| 5 | +/// Implementors of this trait must maintain the following two invariants: |
| 6 | +/// |
| 7 | +/// 1. The concrete data must be a member of a finite-height lattice. |
| 8 | +/// 2. The concrete `constrain` method must be monotone: that is, |
| 9 | +/// if `x <= y`, then `constrain(x) <= constrain(y)`. |
| 10 | +/// |
| 11 | +/// If these invariants do not hold, iteration to a fix-point might never |
| 12 | +/// complete. |
| 13 | +/// |
| 14 | +/// For a simple example analysis, see the `ReachableFrom` type in the `tests` |
| 15 | +/// module below. |
| 16 | +pub trait MonotoneFramework: Sized + fmt::Debug { |
| 17 | + /// The type of node in our dependency graph. |
| 18 | + /// |
| 19 | + /// This is just generic (and not `ItemId`) so that we can easily unit test |
| 20 | + /// without constructing real `Item`s and their `ItemId`s. |
| 21 | + type Node: Copy; |
| 22 | + |
| 23 | + /// Any extra data that is needed during computation. |
| 24 | + /// |
| 25 | + /// Again, this is just generic (and not `&BindgenContext`) so that we can |
| 26 | + /// easily unit test without constructing real `BindgenContext`s full of |
| 27 | + /// real `Item`s and real `ItemId`s. |
| 28 | + type Extra: Sized; |
| 29 | + |
| 30 | + /// The final output of this analysis. Once we have reached a fix-point, we |
| 31 | + /// convert `self` into this type, and return it as the final result of the |
| 32 | + /// analysis. |
| 33 | + type Output: From<Self> + fmt::Debug; |
| 34 | + |
| 35 | + /// Construct a new instance of this analysis. |
| 36 | + fn new(extra: Self::Extra) -> Self; |
| 37 | + |
| 38 | + /// Get the initial set of nodes from which to start the analysis. Unless |
| 39 | + /// you are sure of some domain-specific knowledge, this should be the |
| 40 | + /// complete set of nodes. |
| 41 | + fn initial_worklist(&self) -> Vec<Self::Node>; |
| 42 | + |
| 43 | + /// Update the analysis for the given node. |
| 44 | + /// |
| 45 | + /// If this results in changing our internal state (ie, we discovered that |
| 46 | + /// we have not reached a fix-point and iteration should continue), return |
| 47 | + /// `true`. Otherwise, return `false`. When `constrain` returns false for |
| 48 | + /// all nodes in the set, we have reached a fix-point and the analysis is |
| 49 | + /// complete. |
| 50 | + fn constrain(&mut self, node: Self::Node) -> bool; |
| 51 | + |
| 52 | + /// For each node `d` that depends on the given `node`'s current answer when |
| 53 | + /// running `constrain(d)`, call `f(d)`. This informs us which new nodes to |
| 54 | + /// queue up in the worklist when `constrain(node)` reports updated |
| 55 | + /// information. |
| 56 | + fn each_depending_on<F>(&self, node: Self::Node, f: F) |
| 57 | + where F: FnMut(Self::Node); |
| 58 | +} |
| 59 | + |
| 60 | +/// Run an analysis in the monotone framework. |
| 61 | +pub fn analyze<Analysis>(extra: Analysis::Extra) -> Analysis::Output |
| 62 | + where Analysis: MonotoneFramework, |
| 63 | +{ |
| 64 | + let mut analysis = Analysis::new(extra); |
| 65 | + let mut worklist = analysis.initial_worklist(); |
| 66 | + |
| 67 | + while let Some(node) = worklist.pop() { |
| 68 | + if analysis.constrain(node) { |
| 69 | + analysis.each_depending_on(node, |needs_work| { |
| 70 | + worklist.push(needs_work); |
| 71 | + }); |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + analysis.into() |
| 76 | +} |
| 77 | + |
| 78 | +#[cfg(test)] |
| 79 | +mod tests { |
| 80 | + use super::*; |
| 81 | + use std::collections::{HashMap, HashSet}; |
| 82 | + |
| 83 | + // Here we find the set of nodes that are reachable from any given |
| 84 | + // node. This is a lattice mapping nodes to subsets of all nodes. Our join |
| 85 | + // function is set union. |
| 86 | + // |
| 87 | + // This is our test graph: |
| 88 | + // |
| 89 | + // +---+ +---+ |
| 90 | + // | | | | |
| 91 | + // | 1 | .----| 2 | |
| 92 | + // | | | | | |
| 93 | + // +---+ | +---+ |
| 94 | + // | | ^ |
| 95 | + // | | | |
| 96 | + // | +---+ '------' |
| 97 | + // '----->| | |
| 98 | + // | 3 | |
| 99 | + // .------| |------. |
| 100 | + // | +---+ | |
| 101 | + // | ^ | |
| 102 | + // v | v |
| 103 | + // +---+ | +---+ +---+ |
| 104 | + // | | | | | | | |
| 105 | + // | 4 | | | 5 |--->| 6 | |
| 106 | + // | | | | | | | |
| 107 | + // +---+ | +---+ +---+ |
| 108 | + // | | | | |
| 109 | + // | | | v |
| 110 | + // | +---+ | +---+ |
| 111 | + // | | | | | | |
| 112 | + // '----->| 7 |<-----' | 8 | |
| 113 | + // | | | | |
| 114 | + // +---+ +---+ |
| 115 | + // |
| 116 | + // And here is the mapping from a node to the set of nodes that are |
| 117 | + // reachable from it within the test graph: |
| 118 | + // |
| 119 | + // 1: {3,4,5,6,7,8} |
| 120 | + // 2: {2} |
| 121 | + // 3: {3,4,5,6,7,8} |
| 122 | + // 4: {3,4,5,6,7,8} |
| 123 | + // 5: {3,4,5,6,7,8} |
| 124 | + // 6: {8} |
| 125 | + // 7: {3,4,5,6,7,8} |
| 126 | + // 8: {} |
| 127 | + |
| 128 | + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] |
| 129 | + struct Node(usize); |
| 130 | + |
| 131 | + #[derive(Clone, Debug, Default, PartialEq, Eq)] |
| 132 | + struct Graph(HashMap<Node, Vec<Node>>); |
| 133 | + |
| 134 | + impl Graph { |
| 135 | + fn make_test_graph() -> Graph { |
| 136 | + let mut g = Graph::default(); |
| 137 | + g.0.insert(Node(1), vec![Node(3)]); |
| 138 | + g.0.insert(Node(2), vec![Node(2)]); |
| 139 | + g.0.insert(Node(3), vec![Node(4), Node(5)]); |
| 140 | + g.0.insert(Node(4), vec![Node(7)]); |
| 141 | + g.0.insert(Node(5), vec![Node(6), Node(7)]); |
| 142 | + g.0.insert(Node(6), vec![Node(8)]); |
| 143 | + g.0.insert(Node(7), vec![Node(3)]); |
| 144 | + g.0.insert(Node(8), vec![]); |
| 145 | + g |
| 146 | + } |
| 147 | + |
| 148 | + fn reverse(&self) -> Graph { |
| 149 | + let mut reversed = Graph::default(); |
| 150 | + for (node, edges) in self.0.iter() { |
| 151 | + reversed.0.entry(*node).or_insert(vec![]); |
| 152 | + for referent in edges.iter() { |
| 153 | + reversed.0.entry(*referent).or_insert(vec![]).push(*node); |
| 154 | + } |
| 155 | + } |
| 156 | + reversed |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + #[derive(Clone, Debug, PartialEq, Eq)] |
| 161 | + struct ReachableFrom<'a> { |
| 162 | + reachable: HashMap<Node, HashSet<Node>>, |
| 163 | + graph: &'a Graph, |
| 164 | + reversed: Graph, |
| 165 | + } |
| 166 | + |
| 167 | + impl<'a> MonotoneFramework for ReachableFrom<'a> { |
| 168 | + type Node = Node; |
| 169 | + type Extra = &'a Graph; |
| 170 | + type Output = HashMap<Node, HashSet<Node>>; |
| 171 | + |
| 172 | + fn new(graph: &'a Graph) -> ReachableFrom { |
| 173 | + let reversed = graph.reverse(); |
| 174 | + ReachableFrom { |
| 175 | + reachable: Default::default(), |
| 176 | + graph: graph, |
| 177 | + reversed: reversed, |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + fn initial_worklist(&self) -> Vec<Node> { |
| 182 | + self.graph.0.keys().cloned().collect() |
| 183 | + } |
| 184 | + |
| 185 | + fn constrain(&mut self, node: Node) -> bool { |
| 186 | + // The set of nodes reachable from a node `x` is |
| 187 | + // |
| 188 | + // reachable(x) = s_0 U s_1 U ... U reachable(s_0) U reachable(s_1) U ... |
| 189 | + // |
| 190 | + // where there exist edges from `x` to each of `s_0, s_1, ...`. |
| 191 | + // |
| 192 | + // Yes, what follows is a **terribly** inefficient set union |
| 193 | + // implementation. Don't copy this code outside of this test! |
| 194 | + |
| 195 | + let original_size = |
| 196 | + self.reachable.entry(node).or_insert(HashSet::new()).len(); |
| 197 | + |
| 198 | + for sub_node in self.graph.0[&node].iter() { |
| 199 | + self.reachable.get_mut(&node).unwrap().insert(*sub_node); |
| 200 | + |
| 201 | + let sub_reachable = self.reachable |
| 202 | + .entry(*sub_node) |
| 203 | + .or_insert(HashSet::new()) |
| 204 | + .clone(); |
| 205 | + |
| 206 | + for transitive in sub_reachable { |
| 207 | + self.reachable.get_mut(&node).unwrap().insert(transitive); |
| 208 | + } |
| 209 | + } |
| 210 | + |
| 211 | + let new_size = self.reachable[&node].len(); |
| 212 | + original_size != new_size |
| 213 | + } |
| 214 | + |
| 215 | + fn each_depending_on<F>(&self, node: Node, mut f: F) |
| 216 | + where F: FnMut(Node), |
| 217 | + { |
| 218 | + for dep in self.reversed.0[&node].iter() { |
| 219 | + f(*dep); |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + impl<'a> From<ReachableFrom<'a>> for HashMap<Node, HashSet<Node>> { |
| 225 | + fn from(reachable: ReachableFrom<'a>) -> Self { |
| 226 | + reachable.reachable |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + #[test] |
| 231 | + fn monotone() { |
| 232 | + let g = Graph::make_test_graph(); |
| 233 | + let reachable = analyze::<ReachableFrom>(&g); |
| 234 | + println!("reachable = {:#?}", reachable); |
| 235 | + |
| 236 | + fn nodes<A>(nodes: A) -> HashSet<Node> |
| 237 | + where A: AsRef<[usize]>, |
| 238 | + { |
| 239 | + nodes.as_ref().iter().cloned().map(Node).collect() |
| 240 | + } |
| 241 | + |
| 242 | + let mut expected = HashMap::new(); |
| 243 | + expected.insert(Node(1), nodes([3, 4, 5, 6, 7, 8])); |
| 244 | + expected.insert(Node(2), nodes([2])); |
| 245 | + expected.insert(Node(3), nodes([3, 4, 5, 6, 7, 8])); |
| 246 | + expected.insert(Node(4), nodes([3, 4, 5, 6, 7, 8])); |
| 247 | + expected.insert(Node(5), nodes([3, 4, 5, 6, 7, 8])); |
| 248 | + expected.insert(Node(6), nodes([8])); |
| 249 | + expected.insert(Node(7), nodes([3, 4, 5, 6, 7, 8])); |
| 250 | + expected.insert(Node(8), nodes([])); |
| 251 | + println!("expected = {:#?}", expected); |
| 252 | + |
| 253 | + assert_eq!(reachable, expected); |
| 254 | + } |
| 255 | +} |
0 commit comments