From 7ee3bfd3e15760b777b9332afa1971f4cf0eb90a Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 18 Nov 2025 10:53:32 +0000 Subject: [PATCH 1/6] Add hugr-core StaticGraph --- hugr-core/src/lib.rs | 1 + hugr-core/src/static_graph.rs | 140 ++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 hugr-core/src/static_graph.rs diff --git a/hugr-core/src/lib.rs b/hugr-core/src/lib.rs index 862b8dee8a..8d9bc7d57f 100644 --- a/hugr-core/src/lib.rs +++ b/hugr-core/src/lib.rs @@ -10,6 +10,7 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] pub mod builder; +pub mod static_graph; pub mod core; pub mod envelope; pub mod export; diff --git a/hugr-core/src/static_graph.rs b/hugr-core/src/static_graph.rs new file mode 100644 index 0000000000..05a44db54d --- /dev/null +++ b/hugr-core/src/static_graph.rs @@ -0,0 +1,140 @@ +//! Data structure summarizing static nodes of a Hugr and their uses +use std::collections::HashMap; + +use crate::{HugrView, Node, core::HugrNode, ops::OpType}; +use petgraph::{Graph, visit::EdgeRef}; + +/// Weight for an edge in a [`StaticGraph`] +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum StaticEdge { + /// Edge corresponds to a [Call](OpType::Call) node (specified) in the Hugr + Call(N), + /// Edge corresponds to a [`LoadFunction`](OpType::LoadFunction) node (specified) in the Hugr + LoadFunction(N), + /// Edge corresponds to a [LoadConstant](OpType::LoadConstant) node (specified) in the Hugr + LoadConstant(N), +} + +/// Weight for a petgraph-node in a [`StaticGraph`] +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum StaticNode { + /// petgraph-node corresponds to a [`FuncDecl`](OpType::FuncDecl) node (specified) in the Hugr + FuncDecl(N), + /// petgraph-node corresponds to a [`FuncDefn`](OpType::FuncDefn) node (specified) in the Hugr + FuncDefn(N), + /// petgraph-node corresponds to the [HugrView::entrypoint], that is not + /// a [`FuncDefn`](OpType::FuncDefn). Note that it will not be a [Module](OpType::Module) + /// either, as such a node could not have edges, so is not represented in the petgraph. + NonFuncEntrypoint, + /// petgraph-node corresponds to a constant; will have no outgoing edges, and incoming + /// edges will be [StaticEdge::LoadConstant] + Const(N), +} + +/// Details the [`FuncDefn`]s, [`FuncDecl`]s and module-level [`Const`]s in a Hugr, +/// in a Hugr, along with the [`Call`]s, [`LoadFunction`]s, and [`LoadConstant`]s connecting them. +/// +/// Each node in the `StaticGraph` corresponds to a module-level function or const; +/// each edge corresponds to a use of the target contained in the edge's source. +/// +/// For Hugrs whose entrypoint is neither a [Module](OpType::Module) nor a [`FuncDefn`], +/// the static graph will have an additional [`StaticNode::NonFuncEntrypoint`] +/// corresponding to the Hugr's entrypoint, with no incoming edges. +/// +/// [`Call`]: OpType::Call +/// [`Const`]: OpType::Const +/// [`FuncDecl`]: OpType::FuncDecl +/// [`FuncDefn`]: OpType::FuncDefn +/// [`LoadConstant`]: OpType::LoadConstant +/// [`LoadFunction`]: OpType::LoadFunction +pub struct StaticGraph { + g: Graph, StaticEdge>, + node_to_g: HashMap>, +} + +impl StaticGraph { + /// Makes a new `CallGraph` for a Hugr. + pub fn new(hugr: &impl HugrView) -> Self { + let mut g = Graph::default(); + let mut node_to_g = hugr + .children(hugr.module_root()) + .filter_map(|n| { + let weight = match hugr.get_optype(n) { + OpType::FuncDecl(_) => StaticNode::FuncDecl(n), + OpType::FuncDefn(_) => StaticNode::FuncDefn(n), + OpType::Const(_) => StaticNode::Const(n), + _ => return None, + }; + Some((n, g.add_node(weight))) + }) + .collect::>(); + if !hugr.entrypoint_optype().is_module() && !node_to_g.contains_key(&hugr.entrypoint()) { + node_to_g.insert( + hugr.entrypoint(), + g.add_node(StaticNode::NonFuncEntrypoint), + ); + } + for (func, cg_node) in &node_to_g { + traverse(hugr, *cg_node, *func, &mut g, &node_to_g); + } + fn traverse( + h: &impl HugrView, + enclosing_func: petgraph::graph::NodeIndex, + node: N, // Nonstrict-descendant of `enclosing_func`` + g: &mut Graph, StaticEdge>, + node_to_g: &HashMap>, + ) { + for ch in h.children(node) { + traverse(h, enclosing_func, ch, g, node_to_g); + let weight = match h.get_optype(ch) { + OpType::Call(_) => StaticEdge::Call(ch), + OpType::LoadFunction(_) => StaticEdge::LoadFunction(ch), + OpType::LoadConstant(_) => StaticEdge::LoadConstant(ch), + _ => continue, + }; + if let Some(target) = h.static_source(ch) { + if h.get_parent(target) == Some(h.module_root()) { + g.add_edge(enclosing_func, node_to_g[&target], weight); + } else { + assert!(!node_to_g.contains_key(&target)); + assert!(h.get_optype(ch).is_load_constant()); + assert!(h.get_optype(target).is_const()); + } + } + } + } + StaticGraph { g, node_to_g } + } + + /// Allows access to the petgraph + #[must_use] + pub fn graph(&self) -> &Graph, StaticEdge> { + &self.g + } + + /// Convert a Hugr [Node] into a petgraph node index. + /// Result will be `None` if `n` is not a [`FuncDefn`](OpType::FuncDefn), + /// [`FuncDecl`](OpType::FuncDecl) or the [HugrView::entrypoint]. + pub fn node_index(&self, n: N) -> Option> { + self.node_to_g.get(&n).copied() + } + + /// Returns an iterator over the out-edges from the given Node, i.e. + /// edges to the functions/constants called/loaded by it. + /// + /// If the node is not recognised as a function or the entrypoint, + /// for example if it is a [`Const`](OpType::Const), the iterator will be empty. + pub fn out_edges(&self, n: N) -> impl Iterator, &StaticNode)> { + let g = self.graph(); + self.node_index(n).into_iter().flat_map(move |n| { + self.graph().edges(n).map(|e| { + ( + g.edge_weight(e.id()).unwrap(), + g.node_weight(e.target()).unwrap(), + ) + }) + }) + } +} From 75d0b9c0cdb51e6488eca337b49938719ddecb3c Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 18 Nov 2025 11:58:34 +0000 Subject: [PATCH 2/6] Deprecate hugr-passes CallGraph, migrate dead_funcs/inline_funcs --- hugr-core/src/lib.rs | 2 +- hugr-core/src/static_graph.rs | 5 +---- hugr-passes/src/dead_funcs.rs | 15 ++++++++------- hugr-passes/src/inline_funcs.rs | 31 +++++++++++++++---------------- hugr-passes/src/lib.rs | 1 + 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/hugr-core/src/lib.rs b/hugr-core/src/lib.rs index 8d9bc7d57f..de04068bff 100644 --- a/hugr-core/src/lib.rs +++ b/hugr-core/src/lib.rs @@ -10,7 +10,6 @@ #![cfg_attr(coverage_nightly, feature(coverage_attribute))] pub mod builder; -pub mod static_graph; pub mod core; pub mod envelope; pub mod export; @@ -20,6 +19,7 @@ pub mod import; pub mod macros; pub mod ops; pub mod package; +pub mod static_graph; pub mod std_extensions; pub mod types; pub mod utils; diff --git a/hugr-core/src/static_graph.rs b/hugr-core/src/static_graph.rs index 05a44db54d..d740df1808 100644 --- a/hugr-core/src/static_graph.rs +++ b/hugr-core/src/static_graph.rs @@ -71,10 +71,7 @@ impl StaticGraph { }) .collect::>(); if !hugr.entrypoint_optype().is_module() && !node_to_g.contains_key(&hugr.entrypoint()) { - node_to_g.insert( - hugr.entrypoint(), - g.add_node(StaticNode::NonFuncEntrypoint), - ); + node_to_g.insert(hugr.entrypoint(), g.add_node(StaticNode::NonFuncEntrypoint)); } for (func, cg_node) in &node_to_g { traverse(hugr, *cg_node, *func, &mut g, &node_to_g); diff --git a/hugr-passes/src/dead_funcs.rs b/hugr-passes/src/dead_funcs.rs index 69ae288623..9106db4bcf 100644 --- a/hugr-passes/src/dead_funcs.rs +++ b/hugr-passes/src/dead_funcs.rs @@ -6,6 +6,7 @@ use hugr_core::{ HugrView, Node, hugr::hugrmut::HugrMut, ops::{OpTag, OpTrait}, + static_graph::{StaticGraph, StaticNode}, }; use petgraph::visit::{Dfs, Walker}; @@ -14,8 +15,6 @@ use crate::{ composable::{ValidatePassError, validate_if_test}, }; -use super::call_graph::{CallGraph, CallGraphNode}; - #[derive(Debug, thiserror::Error)] #[non_exhaustive] /// Errors produced by [`RemoveDeadFuncsPass`]. @@ -31,7 +30,7 @@ pub enum RemoveDeadFuncsError { } fn reachable_funcs<'a, H: HugrView>( - cg: &'a CallGraph, + cg: &'a StaticGraph, h: &'a H, entry_points: impl IntoIterator, ) -> impl Iterator + 'a { @@ -41,9 +40,11 @@ fn reachable_funcs<'a, H: HugrView>( for n in entry_points { d.stack.push(cg.node_index(n).unwrap()); } - d.iter(g).map(|i| match g.node_weight(i).unwrap() { - CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, - CallGraphNode::NonFuncRoot => h.entrypoint(), + d.iter(g).filter_map(|i| match g.node_weight(i).unwrap() { + StaticNode::FuncDefn(n) | StaticNode::FuncDecl(n) => Some(*n), + StaticNode::NonFuncEntrypoint => Some(h.entrypoint()), + StaticNode::Const(_) => None, + _ => unreachable!(), }) } @@ -85,7 +86,7 @@ impl> ComposablePass for RemoveDeadFuncsPass { } let mut reachable = - reachable_funcs(&CallGraph::new(hugr), hugr, entry_points).collect::>(); + reachable_funcs(&StaticGraph::new(hugr), hugr, entry_points).collect::>(); // Also prevent removing the entrypoint itself let mut n = Some(hugr.entrypoint()); while let Some(n2) = n { diff --git a/hugr-passes/src/inline_funcs.rs b/hugr-passes/src/inline_funcs.rs index b999560f45..af09d7fca1 100644 --- a/hugr-passes/src/inline_funcs.rs +++ b/hugr-passes/src/inline_funcs.rs @@ -1,12 +1,11 @@ //! Contains a pass to inline calls to selected functions in a Hugr. use std::collections::{HashSet, VecDeque}; -use hugr_core::hugr::hugrmut::HugrMut; -use hugr_core::hugr::patch::inline_call::InlineCall; use itertools::Itertools; use petgraph::algo::tarjan_scc; -use crate::call_graph::{CallGraph, CallGraphNode}; +use hugr_core::hugr::{hugrmut::HugrMut, patch::inline_call::InlineCall}; +use hugr_core::static_graph::{StaticGraph, StaticNode}; /// Error raised by [inline_acyclic] #[derive(Clone, Debug, thiserror::Error, PartialEq)] @@ -26,7 +25,7 @@ pub fn inline_acyclic( h: &mut H, call_predicate: impl Fn(&H, H::Node) -> bool, ) -> Result<(), InlineFuncsError> { - let cg = CallGraph::new(&*h); + let cg = StaticGraph::new(&*h); let g = cg.graph(); let all_funcs_in_cycles = tarjan_scc(g) .into_iter() @@ -37,7 +36,7 @@ pub fn inline_acyclic( } } ns.into_iter().map(|n| { - let CallGraphNode::FuncDefn(fd) = g.node_weight(n).unwrap() else { + let StaticNode::FuncDefn(fd) = g.node_weight(n).unwrap() else { panic!("Expected only FuncDefns in sccs") }; *fd @@ -68,18 +67,18 @@ pub fn inline_acyclic( mod test { use std::collections::HashSet; - use hugr_core::core::HugrNode; - use hugr_core::ops::OpType; use itertools::Itertools; use petgraph::visit::EdgeRef; + use rstest::rstest; use hugr_core::HugrView; use hugr_core::builder::{Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder}; + use hugr_core::core::HugrNode; + use hugr_core::ops::OpType; + use hugr_core::static_graph::{StaticGraph, StaticNode}; use hugr_core::{Hugr, extension::prelude::qb_t, types::Signature}; - use rstest::rstest; - use crate::call_graph::{CallGraph, CallGraphNode}; - use crate::inline_funcs::inline_acyclic; + use super::inline_acyclic; /// /->-\ /// main -> f g -> b -> c @@ -156,7 +155,7 @@ mod test { target_funcs.contains(&tgt) }) .unwrap(); - let cg = CallGraph::new(&h); + let cg = StaticGraph::new(&h); for fname in check_not_called { let fnode = find_func(&h, fname); let fnode = cg.node_index(fnode).unwrap(); @@ -180,7 +179,7 @@ mod test { } } - fn outgoing_calls(cg: &CallGraph, src: N) -> Vec { + fn outgoing_calls(cg: &StaticGraph, src: N) -> Vec { let src = cg.node_index(src).unwrap(); cg.graph() .edges_directed(src, petgraph::Direction::Outgoing) @@ -205,17 +204,17 @@ mod test { } }) .unwrap(); - let cg = CallGraph::new(&h); + let cg = StaticGraph::new(&h); // b and then c should have been inlined into g, leaving only cyclic call to f assert_eq!(outgoing_calls(&cg, g), [find_func(&h, "f")]); // But c should not have been inlined into b: assert_eq!(outgoing_calls(&cg, b), [c]); } - fn func_node(cgn: &CallGraphNode) -> N { + fn func_node(cgn: &StaticNode) -> N { match cgn { - CallGraphNode::FuncDecl(n) | CallGraphNode::FuncDefn(n) => *n, - CallGraphNode::NonFuncRoot => panic!(), + StaticNode::FuncDecl(n) | StaticNode::FuncDefn(n) => *n, + _ => panic!(), } } diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index 2a0cbb01a5..6eea804170 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -1,5 +1,6 @@ //! Compilation passes acting on the HUGR program representation. +#[deprecated(note = "Use hugr-core StaticGraph", since = "0.24.1")] pub mod call_graph; pub mod composable; pub use composable::ComposablePass; From 661a8faf01510835d23a8e9ffd4d59b2e3f0d647 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 18 Nov 2025 17:07:35 +0000 Subject: [PATCH 3/6] StaticGraph -> ModuleGraph but keep StaticNode/StaticEdge --- hugr-core/src/lib.rs | 2 +- hugr-core/src/{static_graph.rs => module_graph.rs} | 14 +++++++------- hugr-passes/src/dead_code.rs | 2 +- hugr-passes/src/dead_funcs.rs | 6 +++--- hugr-passes/src/inline_funcs.rs | 12 ++++++------ hugr-passes/src/lib.rs | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) rename hugr-core/src/{static_graph.rs => module_graph.rs} (94%) diff --git a/hugr-core/src/lib.rs b/hugr-core/src/lib.rs index de04068bff..015be311f8 100644 --- a/hugr-core/src/lib.rs +++ b/hugr-core/src/lib.rs @@ -17,9 +17,9 @@ pub mod extension; pub mod hugr; pub mod import; pub mod macros; +pub mod module_graph; pub mod ops; pub mod package; -pub mod static_graph; pub mod std_extensions; pub mod types; pub mod utils; diff --git a/hugr-core/src/static_graph.rs b/hugr-core/src/module_graph.rs similarity index 94% rename from hugr-core/src/static_graph.rs rename to hugr-core/src/module_graph.rs index d740df1808..c03748a4a0 100644 --- a/hugr-core/src/static_graph.rs +++ b/hugr-core/src/module_graph.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::{HugrView, Node, core::HugrNode, ops::OpType}; use petgraph::{Graph, visit::EdgeRef}; -/// Weight for an edge in a [`StaticGraph`] +/// Weight for an edge in a [`ModuleGraph`] #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum StaticEdge { @@ -16,7 +16,7 @@ pub enum StaticEdge { LoadConstant(N), } -/// Weight for a petgraph-node in a [`StaticGraph`] +/// Weight for a petgraph-node in a [`ModuleGraph`] #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum StaticNode { @@ -36,7 +36,7 @@ pub enum StaticNode { /// Details the [`FuncDefn`]s, [`FuncDecl`]s and module-level [`Const`]s in a Hugr, /// in a Hugr, along with the [`Call`]s, [`LoadFunction`]s, and [`LoadConstant`]s connecting them. /// -/// Each node in the `StaticGraph` corresponds to a module-level function or const; +/// Each node in the `ModuleGraph` corresponds to a module-level function or const; /// each edge corresponds to a use of the target contained in the edge's source. /// /// For Hugrs whose entrypoint is neither a [Module](OpType::Module) nor a [`FuncDefn`], @@ -49,13 +49,13 @@ pub enum StaticNode { /// [`FuncDefn`]: OpType::FuncDefn /// [`LoadConstant`]: OpType::LoadConstant /// [`LoadFunction`]: OpType::LoadFunction -pub struct StaticGraph { +pub struct ModuleGraph { g: Graph, StaticEdge>, node_to_g: HashMap>, } -impl StaticGraph { - /// Makes a new `CallGraph` for a Hugr. +impl ModuleGraph { + /// Makes a new `ModuleGraph` for a Hugr. pub fn new(hugr: &impl HugrView) -> Self { let mut g = Graph::default(); let mut node_to_g = hugr @@ -102,7 +102,7 @@ impl StaticGraph { } } } - StaticGraph { g, node_to_g } + ModuleGraph { g, node_to_g } } /// Allows access to the petgraph diff --git a/hugr-passes/src/dead_code.rs b/hugr-passes/src/dead_code.rs index d6390dbf96..094d3e8058 100644 --- a/hugr-passes/src/dead_code.rs +++ b/hugr-passes/src/dead_code.rs @@ -67,7 +67,7 @@ pub enum PreserveNode { impl PreserveNode { /// A conservative default for a given node. Just examines the node's [`OpType`]: /// * Assumes all Calls must be preserved. (One could scan the called `FuncDefn`, but would - /// also need to check for cycles in the [`CallGraph`](super::call_graph::CallGraph).) + /// also need to check for cycles in the [`ModuleGraph`](hugr_core::module_graph::ModuleGraph).) /// * Assumes all CFGs must be preserved. (One could, for example, allow acyclic /// CFGs to be removed.) /// * Assumes all `TailLoops` must be preserved. (One could, for example, use dataflow diff --git a/hugr-passes/src/dead_funcs.rs b/hugr-passes/src/dead_funcs.rs index 9106db4bcf..a77c19fe9e 100644 --- a/hugr-passes/src/dead_funcs.rs +++ b/hugr-passes/src/dead_funcs.rs @@ -5,8 +5,8 @@ use std::collections::HashSet; use hugr_core::{ HugrView, Node, hugr::hugrmut::HugrMut, + module_graph::{ModuleGraph, StaticNode}, ops::{OpTag, OpTrait}, - static_graph::{StaticGraph, StaticNode}, }; use petgraph::visit::{Dfs, Walker}; @@ -30,7 +30,7 @@ pub enum RemoveDeadFuncsError { } fn reachable_funcs<'a, H: HugrView>( - cg: &'a StaticGraph, + cg: &'a ModuleGraph, h: &'a H, entry_points: impl IntoIterator, ) -> impl Iterator + 'a { @@ -86,7 +86,7 @@ impl> ComposablePass for RemoveDeadFuncsPass { } let mut reachable = - reachable_funcs(&StaticGraph::new(hugr), hugr, entry_points).collect::>(); + reachable_funcs(&ModuleGraph::new(hugr), hugr, entry_points).collect::>(); // Also prevent removing the entrypoint itself let mut n = Some(hugr.entrypoint()); while let Some(n2) = n { diff --git a/hugr-passes/src/inline_funcs.rs b/hugr-passes/src/inline_funcs.rs index af09d7fca1..d7f0a96953 100644 --- a/hugr-passes/src/inline_funcs.rs +++ b/hugr-passes/src/inline_funcs.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use petgraph::algo::tarjan_scc; use hugr_core::hugr::{hugrmut::HugrMut, patch::inline_call::InlineCall}; -use hugr_core::static_graph::{StaticGraph, StaticNode}; +use hugr_core::module_graph::{ModuleGraph, StaticNode}; /// Error raised by [inline_acyclic] #[derive(Clone, Debug, thiserror::Error, PartialEq)] @@ -25,7 +25,7 @@ pub fn inline_acyclic( h: &mut H, call_predicate: impl Fn(&H, H::Node) -> bool, ) -> Result<(), InlineFuncsError> { - let cg = StaticGraph::new(&*h); + let cg = ModuleGraph::new(&*h); let g = cg.graph(); let all_funcs_in_cycles = tarjan_scc(g) .into_iter() @@ -75,7 +75,7 @@ mod test { use hugr_core::builder::{Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder}; use hugr_core::core::HugrNode; use hugr_core::ops::OpType; - use hugr_core::static_graph::{StaticGraph, StaticNode}; + use hugr_core::static_graph::{ModuleGraph, StaticNode}; use hugr_core::{Hugr, extension::prelude::qb_t, types::Signature}; use super::inline_acyclic; @@ -155,7 +155,7 @@ mod test { target_funcs.contains(&tgt) }) .unwrap(); - let cg = StaticGraph::new(&h); + let cg = ModuleGraph::new(&h); for fname in check_not_called { let fnode = find_func(&h, fname); let fnode = cg.node_index(fnode).unwrap(); @@ -179,7 +179,7 @@ mod test { } } - fn outgoing_calls(cg: &StaticGraph, src: N) -> Vec { + fn outgoing_calls(cg: &ModuleGraph, src: N) -> Vec { let src = cg.node_index(src).unwrap(); cg.graph() .edges_directed(src, petgraph::Direction::Outgoing) @@ -204,7 +204,7 @@ mod test { } }) .unwrap(); - let cg = StaticGraph::new(&h); + let cg = ModuleGraph::new(&h); // b and then c should have been inlined into g, leaving only cyclic call to f assert_eq!(outgoing_calls(&cg, g), [find_func(&h, "f")]); // But c should not have been inlined into b: diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index 6eea804170..f499973c3c 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -1,6 +1,6 @@ //! Compilation passes acting on the HUGR program representation. -#[deprecated(note = "Use hugr-core StaticGraph", since = "0.24.1")] +#[deprecated(note = "Use hugr-core::module_graph::ModuleGraph", since = "0.24.1")] pub mod call_graph; pub mod composable; pub use composable::ComposablePass; From 9d3c8c3b432ad069aeaa07d9d10184ebea94d92a Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 18 Nov 2025 17:09:58 +0000 Subject: [PATCH 4/6] inline_funcs (test): use out_edges --- hugr-passes/src/inline_funcs.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/hugr-passes/src/inline_funcs.rs b/hugr-passes/src/inline_funcs.rs index d7f0a96953..8b3e073b86 100644 --- a/hugr-passes/src/inline_funcs.rs +++ b/hugr-passes/src/inline_funcs.rs @@ -68,14 +68,13 @@ mod test { use std::collections::HashSet; use itertools::Itertools; - use petgraph::visit::EdgeRef; use rstest::rstest; use hugr_core::HugrView; use hugr_core::builder::{Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder}; use hugr_core::core::HugrNode; + use hugr_core::module_graph::{ModuleGraph, StaticNode}; use hugr_core::ops::OpType; - use hugr_core::static_graph::{ModuleGraph, StaticNode}; use hugr_core::{Hugr, extension::prelude::qb_t, types::Signature}; use super::inline_acyclic; @@ -180,11 +179,7 @@ mod test { } fn outgoing_calls(cg: &ModuleGraph, src: N) -> Vec { - let src = cg.node_index(src).unwrap(); - cg.graph() - .edges_directed(src, petgraph::Direction::Outgoing) - .map(|e| func_node(cg.graph().node_weight(e.target()).unwrap())) - .collect() + cg.out_edges(src).map(|(_, tgt)| func_node(tgt)).collect() } #[test] From e009e8c23984f51d62f972db44e263c3b2a34051 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 18 Nov 2025 17:11:49 +0000 Subject: [PATCH 5/6] Add in_edges --- hugr-core/src/module_graph.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/hugr-core/src/module_graph.rs b/hugr-core/src/module_graph.rs index c03748a4a0..317066c2dd 100644 --- a/hugr-core/src/module_graph.rs +++ b/hugr-core/src/module_graph.rs @@ -134,4 +134,23 @@ impl ModuleGraph { }) }) } + + /// Returns an iterator over the in-edges to the given Node, i.e. + /// edges from the (necessarily) functions that call/load it. + /// + /// If the node is not recognised as a function or constant, + /// for example if it is a non-function entrypoint, the iterator will be empty. + pub fn in_edges(&self, n: N) -> impl Iterator, &StaticEdge)> { + let g = self.graph(); + self.node_index(n).into_iter().flat_map(move |n| { + self.graph() + .edges_directed(n, petgraph::Direction::Incoming) + .map(|e| { + ( + g.node_weight(e.source()).unwrap(), + g.edge_weight(e.id()).unwrap(), + ) + }) + }) + } } From d0a40b3fce07a151b520c78b23d4a0a4a9896aff Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 18 Nov 2025 17:31:53 +0000 Subject: [PATCH 6/6] test --- hugr-core/src/module_graph.rs | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/hugr-core/src/module_graph.rs b/hugr-core/src/module_graph.rs index 317066c2dd..5baec0a6e9 100644 --- a/hugr-core/src/module_graph.rs +++ b/hugr-core/src/module_graph.rs @@ -154,3 +154,57 @@ impl ModuleGraph { }) } } + +#[cfg(test)] +mod test { + use itertools::Itertools as _; + + use crate::builder::{ + Container, Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder, endo_sig, inout_sig, + }; + use crate::extension::prelude::{ConstUsize, usize_t}; + use crate::ops::{Value, handle::NodeHandle}; + + use super::*; + + #[test] + fn edges() { + let mut mb = ModuleBuilder::new(); + let cst = mb.add_constant(Value::from(ConstUsize::new(42))); + let callee = mb.define_function("callee", endo_sig(usize_t())).unwrap(); + let ins = callee.input_wires(); + let callee = callee.finish_with_outputs(ins).unwrap(); + let mut caller = mb + .define_function("caller", inout_sig(vec![], usize_t())) + .unwrap(); + let val = caller.load_const(&cst); + let call = caller.call(callee.handle(), &[], vec![val]).unwrap(); + let caller = caller.finish_with_outputs(call.outputs()).unwrap(); + let h = mb.finish_hugr().unwrap(); + + let mg = ModuleGraph::new(&h); + let call_edge = StaticEdge::Call(call.node()); + let load_const_edge = StaticEdge::LoadConstant(val.node()); + + assert_eq!(mg.out_edges(callee.node()).next(), None); + assert_eq!( + mg.in_edges(callee.node()).collect_vec(), + [(&StaticNode::FuncDefn(caller.node()), &call_edge,)] + ); + + assert_eq!( + mg.out_edges(caller.node()).collect_vec(), + [ + (&call_edge, &StaticNode::FuncDefn(callee.node()),), + (&load_const_edge, &StaticNode::Const(cst.node()),) + ] + ); + assert_eq!(mg.in_edges(caller.node()).next(), None); + + assert_eq!(mg.out_edges(cst.node()).next(), None); + assert_eq!( + mg.in_edges(cst.node()).collect_vec(), + [(&StaticNode::FuncDefn(caller.node()), &load_const_edge,)] + ); + } +}