Skip to content

Commit f9a3446

Browse files
authored
feat: Dataflow analysis framework (#1476)
Forwards analysis only, but parametrized over the abstract domain, hence intended to support various applications including constant folding and potentially devirtualization, intergraph-edge-insertion, etc. Much complexity is to do with "native" (irrespective of the underlying domain) treatment of Sum types necessary for proper understanding of control flow (e.g. conditionals, loops, CFGs). Generalization of reachability left to #1672 .
1 parent fee16d3 commit f9a3446

File tree

8 files changed

+2009
-0
lines changed

8 files changed

+2009
-0
lines changed

hugr-passes/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ bench = false
1717

1818
[dependencies]
1919
hugr-core = { path = "../hugr-core", version = "0.13.3" }
20+
ascent = { version = "0.7.0" }
2021
itertools = { workspace = true }
2122
lazy_static = { workspace = true }
2223
paste = { workspace = true }
@@ -28,3 +29,6 @@ extension_inference = ["hugr-core/extension_inference"]
2829

2930
[dev-dependencies]
3031
rstest = { workspace = true }
32+
proptest = { workspace = true }
33+
proptest-derive = { workspace = true }
34+
proptest-recurse = { version = "0.5.0" }

hugr-passes/src/dataflow.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#![warn(missing_docs)]
2+
//! Dataflow analysis of Hugrs.
3+
4+
mod datalog;
5+
pub use datalog::Machine;
6+
mod value_row;
7+
8+
mod results;
9+
pub use results::{AnalysisResults, TailLoopTermination};
10+
11+
mod partial_value;
12+
pub use partial_value::{AbstractValue, PartialSum, PartialValue, Sum};
13+
14+
use hugr_core::ops::constant::OpaqueValue;
15+
use hugr_core::ops::{ExtensionOp, Value};
16+
use hugr_core::types::TypeArg;
17+
use hugr_core::{Hugr, Node};
18+
19+
/// Clients of the dataflow framework (particular analyses, such as constant folding)
20+
/// must implement this trait (including providing an appropriate domain type `V`).
21+
pub trait DFContext<V>: ConstLoader<V> {
22+
/// Given lattice values for each input, update lattice values for the (dataflow) outputs.
23+
/// For extension ops only, excluding [MakeTuple] and [UnpackTuple] which are handled automatically.
24+
/// `_outs` is an array with one element per dataflow output, each initialized to [PartialValue::Top]
25+
/// which is the correct value to leave if nothing can be deduced about that output.
26+
/// (The default does nothing, i.e. leaves `Top` for all outputs.)
27+
///
28+
/// [MakeTuple]: hugr_core::extension::prelude::MakeTuple
29+
/// [UnpackTuple]: hugr_core::extension::prelude::UnpackTuple
30+
fn interpret_leaf_op(
31+
&mut self,
32+
_node: Node,
33+
_e: &ExtensionOp,
34+
_ins: &[PartialValue<V>],
35+
_outs: &mut [PartialValue<V>],
36+
) {
37+
}
38+
}
39+
40+
/// A location where a [Value] could be find in a Hugr. That is,
41+
/// (perhaps deeply nested within [Value::Sum]s) within a [Node]
42+
/// that is a [Const](hugr_core::ops::Const).
43+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44+
pub enum ConstLocation<'a> {
45+
/// The specified-index'th field of the [Value::Sum] constant identified by the RHS
46+
Field(usize, &'a ConstLocation<'a>),
47+
/// The entire ([Const::value](hugr_core::ops::Const::value)) of the node.
48+
Node(Node),
49+
}
50+
51+
impl From<Node> for ConstLocation<'_> {
52+
fn from(value: Node) -> Self {
53+
ConstLocation::Node(value)
54+
}
55+
}
56+
57+
/// Trait for loading [PartialValue]s from constant [Value]s in a Hugr.
58+
/// Implementors will likely want to override some/all of [Self::value_from_opaque],
59+
/// [Self::value_from_const_hugr], and [Self::value_from_function]: the defaults
60+
/// are "correct" but maximally conservative (minimally informative).
61+
pub trait ConstLoader<V> {
62+
/// Produces an abstract value from an [OpaqueValue], if possible.
63+
/// The default just returns `None`, which will be interpreted as [PartialValue::Top].
64+
fn value_from_opaque(&self, _loc: ConstLocation, _val: &OpaqueValue) -> Option<V> {
65+
None
66+
}
67+
68+
/// Produces an abstract value from a Hugr in a [Value::Function], if possible.
69+
/// The default just returns `None`, which will be interpreted as [PartialValue::Top].
70+
fn value_from_const_hugr(&self, _loc: ConstLocation, _h: &Hugr) -> Option<V> {
71+
None
72+
}
73+
74+
/// Produces an abstract value from a [FuncDefn] or [FuncDecl] node
75+
/// (that has been loaded via a [LoadFunction]), if possible.
76+
/// The default just returns `None`, which will be interpreted as [PartialValue::Top].
77+
///
78+
/// [FuncDefn]: hugr_core::ops::FuncDefn
79+
/// [FuncDecl]: hugr_core::ops::FuncDecl
80+
/// [LoadFunction]: hugr_core::ops::LoadFunction
81+
fn value_from_function(&self, _node: Node, _type_args: &[TypeArg]) -> Option<V> {
82+
None
83+
}
84+
}
85+
86+
/// Produces a [PartialValue] from a constant. Traverses [Sum](Value::Sum) constants
87+
/// to their leaves ([Value::Extension] and [Value::Function]),
88+
/// converts these using [ConstLoader::value_from_opaque] and [ConstLoader::value_from_const_hugr],
89+
/// and builds nested [PartialValue::new_variant] to represent the structure.
90+
fn partial_from_const<'a, V>(
91+
cl: &impl ConstLoader<V>,
92+
loc: impl Into<ConstLocation<'a>>,
93+
cst: &Value,
94+
) -> PartialValue<V> {
95+
let loc = loc.into();
96+
match cst {
97+
Value::Sum(hugr_core::ops::constant::Sum { tag, values, .. }) => {
98+
let elems = values
99+
.iter()
100+
.enumerate()
101+
.map(|(idx, elem)| partial_from_const(cl, ConstLocation::Field(idx, &loc), elem));
102+
PartialValue::new_variant(*tag, elems)
103+
}
104+
Value::Extension { e } => cl
105+
.value_from_opaque(loc, e)
106+
.map(PartialValue::from)
107+
.unwrap_or(PartialValue::Top),
108+
Value::Function { hugr } => cl
109+
.value_from_const_hugr(loc, hugr)
110+
.map(PartialValue::from)
111+
.unwrap_or(PartialValue::Top),
112+
}
113+
}
114+
115+
/// A row of inputs to a node contains bottom (can't happen, the node
116+
/// can't execute) if any element [contains_bottom](PartialValue::contains_bottom).
117+
pub fn row_contains_bottom<'a, V: AbstractValue + 'a>(
118+
elements: impl IntoIterator<Item = &'a PartialValue<V>>,
119+
) -> bool {
120+
elements.into_iter().any(PartialValue::contains_bottom)
121+
}
122+
123+
#[cfg(test)]
124+
mod test;

0 commit comments

Comments
 (0)