Skip to content

Commit 718d367

Browse files
author
zzhu
committed
Separate the analysis framework in a different file.
1 parent e794cd4 commit 718d367

File tree

3 files changed

+257
-255
lines changed

3 files changed

+257
-255
lines changed

src/ir/analysis.rs

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
}

src/ir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! the IR.
55
66
pub mod annotations;
7+
pub mod analysis;
78
pub mod comp;
89
pub mod context;
910
pub mod derive;

0 commit comments

Comments
 (0)