From 3a0ee566ad2038f3ac59835f891ad5f3dffd123f Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 11:28:55 +0000 Subject: [PATCH 01/22] Add call_graph.rs, start writing docs --- hugr-passes/src/call_graph.rs | 110 ++++++++++++++++++++++++++++++++++ hugr-passes/src/lib.rs | 3 +- 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 hugr-passes/src/call_graph.rs diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs new file mode 100644 index 000000000..c89db7d49 --- /dev/null +++ b/hugr-passes/src/call_graph.rs @@ -0,0 +1,110 @@ +//! Data structure for call graphs of a Hugr, and some transformations using them. +use std::collections::{HashMap, HashSet}; + +use hugr_core::{ + hugr::hugrmut::HugrMut, + ops::{OpTag, OpTrait, OpType}, + HugrView, Node, +}; +use petgraph::{graph::NodeIndex, visit::Bfs, Graph}; + +enum CallGraphEdge { + Call(Node), + LoadFunction(Node), +} + +pub struct CallGraph { + g: Graph, + node_to_g: HashMap>, +} + +impl CallGraph { + /// Makes a new CallGraph for a specified (subview) of a Hugr. + /// Calls to functions outside the view will be dropped. + pub fn new(hugr: &impl HugrView) -> Self { + let mut g = Graph::default(); + let root = (!hugr.get_optype(hugr.root()).is_module()).then_some(hugr.root()); + let node_to_g = hugr + .nodes() + // For non-Module-rooted Hugrs, make sure we include the root + .filter(|&n| Some(n) == root || OpTag::Function.is_superset(hugr.get_optype(n).tag())) + .map(|n| (n, g.add_node(n))) + .collect::>(); + for (func, cg_node) in node_to_g.iter() { + traverse(hugr, *func, *cg_node, &mut g, &node_to_g) + } + fn traverse( + h: &impl HugrView, + node: Node, + enclosing: NodeIndex, + g: &mut Graph, + node_to_g: &HashMap>, + ) { + for ch in h.children(node) { + if h.get_optype(ch).is_func_defn() { + continue; + }; + traverse(h, ch, enclosing, g, node_to_g); + let weight = match h.get_optype(ch) { + OpType::Call(_) => CallGraphEdge::Call(ch), + OpType::LoadFunction(_) => CallGraphEdge::LoadFunction(ch), + _ => continue, + }; + if let Some(target) = h.static_source(ch) { + g.add_edge(enclosing, *node_to_g.get(&target).unwrap(), weight); + } + } + } + CallGraph { g, node_to_g } + } + + fn reachable_funcs( + &self, + h: &impl HugrView, + roots: Option>, + ) -> impl Iterator + '_ { + let mut b = if h.get_optype(h.root()).is_module() { + let mut entry_points = match roots { + Some(i) => i.into_iter().collect(), + None => h + .children(h.root()) + .filter(|n| { + h.get_optype(*n) + .as_func_defn() + .is_some_and(|fd| fd.name == "main") + }) + .collect::>(), + } + .into_iter() + .map(|i| self.node_to_g.get(&i).unwrap()); + let mut b = Bfs::new(&self.g, *entry_points.next().unwrap()); + while let Some(e) = entry_points.next() { + b.stack.push_back(*e); + } + b + } else { + Bfs::new(&self.g, *self.node_to_g.get(&h.root()).unwrap()) + }; + std::iter::from_fn(move || b.next(&self.g)).map(|i| *self.g.node_weight(i).unwrap()) + } +} + +/// Delete from the Hugr any functions that are not used by either [Call](OpType::Call) or +/// [LoadFunction](OpType::LoadFunction) nodes in reachable parts. +/// +/// For [Module](OpType::Module)-rooted Hugrs, `roots` may provide a list of entry points; +/// these are expected to be children of the root although this is not enforced. If `roots` +/// is absent, then the function called `main` (must be present and) is used as sole entry point. +/// +/// +pub fn remove_dead_funcs(h: &mut impl HugrMut, roots: Option>) { + let cg = CallGraph::new(h); + let reachable = cg.reachable_funcs(h, roots).collect::>(); + let unreachable = h + .nodes() + .filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n)) + .collect::>(); + for n in unreachable { + h.remove_subtree(n); + } +} diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index a4507e156..5da8c68a2 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. +pub mod call_graph; pub mod const_fold; pub mod dataflow; pub mod force_order; @@ -7,7 +8,7 @@ mod half_node; pub mod lower; pub mod merge_bbs; mod monomorphize; -pub use monomorphize::{monomorphize, remove_polyfuncs}; +pub use monomorphize::monomorphize; pub mod nest_cfgs; pub mod non_local; pub mod validation; From fcd5321e3505c8c2742b4fbc874042b4cb527c88 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 12:18:02 +0000 Subject: [PATCH 02/22] roots == Some(empty) meaningless => make non-Opt; pub CallGraphEdge; docs --- hugr-passes/src/call_graph.rs | 64 ++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index c89db7d49..ca8dd89ca 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -6,13 +6,27 @@ use hugr_core::{ ops::{OpTag, OpTrait, OpType}, HugrView, Node, }; +use itertools::Itertools; use petgraph::{graph::NodeIndex, visit::Bfs, Graph}; -enum CallGraphEdge { +/// Weight for an edge in a [CallGraph] +pub enum CallGraphEdge { + /// Edge corresponds to a [Call](OpType::Call) node (specified) in the Hugr Call(Node), + /// Edge corresponds to a [LoadFunction](OpType::LoadFunction) node (specified) in the Hugr LoadFunction(Node), } +/// Details the [Call]s and [LoadFunction]s in a Hugr. +/// Each node in the `CallGraph` corresponds to a [FuncDefn] in the Hugr; each edge corresponds +/// to a [Call]/[LoadFunction] of the edge's target, contained in the edge's source. +/// +/// For Hugrs whose root is neither a [Module](OpType::Module) nor a [FuncDefn], the call graph +/// will have an additional node corresponding to the Hugr's root, with no incoming edges. +/// +/// [Call]: OpType::Call +/// [FuncDefn]: OpType::FuncDefn +/// [LoadFunction]: OpType::LoadFunction pub struct CallGraph { g: Graph, node_to_g: HashMap>, @@ -61,28 +75,31 @@ impl CallGraph { fn reachable_funcs( &self, h: &impl HugrView, - roots: Option>, + roots: impl IntoIterator, ) -> impl Iterator + '_ { + let mut entry_points = roots.into_iter().collect_vec(); let mut b = if h.get_optype(h.root()).is_module() { - let mut entry_points = match roots { - Some(i) => i.into_iter().collect(), - None => h - .children(h.root()) - .filter(|n| { - h.get_optype(*n) - .as_func_defn() - .is_some_and(|fd| fd.name == "main") - }) - .collect::>(), + if entry_points.is_empty() { + entry_points.push( + h.children(h.root()) + .filter(|n| { + h.get_optype(*n) + .as_func_defn() + .is_some_and(|fd| fd.name == "main") + }) + .exactly_one() + .ok() + .expect("No entry_points provided, Module must contain `main`"), + ); } - .into_iter() - .map(|i| self.node_to_g.get(&i).unwrap()); + let mut entry_points = entry_points + .into_iter() + .map(|i| self.node_to_g.get(&i).unwrap()); let mut b = Bfs::new(&self.g, *entry_points.next().unwrap()); - while let Some(e) = entry_points.next() { - b.stack.push_back(*e); - } + b.stack.extend(entry_points); b } else { + assert!(entry_points.is_empty()); Bfs::new(&self.g, *self.node_to_g.get(&h.root()).unwrap()) }; std::iter::from_fn(move || b.next(&self.g)).map(|i| *self.g.node_weight(i).unwrap()) @@ -94,12 +111,19 @@ impl CallGraph { /// /// For [Module](OpType::Module)-rooted Hugrs, `roots` may provide a list of entry points; /// these are expected to be children of the root although this is not enforced. If `roots` -/// is absent, then the function called `main` (must be present and) is used as sole entry point. +/// is empty, then the root must have exactly one child being a function called `main`, +/// which is used as sole entry point. /// +/// For non-Module-rooted Hugrs, `entry_points` must be empty; the root node is used. /// -pub fn remove_dead_funcs(h: &mut impl HugrMut, roots: Option>) { +/// # Panics +/// * If the Hugr is non-Module-rooted and `entry_points` is non-empty +/// * If the Hugr is Module-rooted, but does not declare `main`, and `entry_points` is empty +/// * If the Hugr is Module-rooted, and `entry_points` is non-empty but contains nodes that +/// are not [FuncDefn](OpType::FuncDefn)s +pub fn remove_dead_funcs(h: &mut impl HugrMut, entry_points: impl IntoIterator) { let cg = CallGraph::new(h); - let reachable = cg.reachable_funcs(h, roots).collect::>(); + let reachable = cg.reachable_funcs(h, entry_points).collect::>(); let unreachable = h .nodes() .filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n)) From c497e4d722bba0555c12d17197401a3bf589e8d0 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 12:30:36 +0000 Subject: [PATCH 03/22] Remove remove_polyfuncs --- hugr-passes/src/monomorphize.rs | 38 ++++++++++----------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index f4144ef72..1bb9185ef 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -13,12 +13,14 @@ use hugr_core::{ use hugr_core::hugr::{hugrmut::HugrMut, internal::HugrMutInternals, Hugr, HugrView, OpType}; use itertools::Itertools as _; +use crate::call_graph::remove_dead_funcs; + /// Replaces calls to polymorphic functions with calls to new monomorphic /// instantiations of the polymorphic ones. /// /// If the Hugr is [Module](OpType::Module)-rooted, /// * then the original polymorphic [FuncDefn]s are left untouched (including Calls inside them) -/// - call [remove_polyfuncs] when no other Hugr will be linked in that might instantiate these +/// - [remove_dead_funcs] can be used when no other Hugr will be linked in that might instantiate these /// * else, the originals are removed (they are invisible from outside the Hugr). /// /// If the Hugr is [FuncDefn](OpType::FuncDefn)-rooted with polymorphic @@ -42,7 +44,7 @@ pub fn monomorphize(mut h: Hugr) -> Hugr { if !is_polymorphic_funcdefn(h.get_optype(root)) { mono_scan(&mut h, root, None, &mut HashMap::new()); if !h.get_optype(root).is_module() { - return remove_polyfuncs(h); + remove_dead_funcs(&mut h, []); } } #[cfg(debug_assertions)] @@ -50,28 +52,6 @@ pub fn monomorphize(mut h: Hugr) -> Hugr { h } -/// Removes any polymorphic [FuncDefn]s from the Hugr. Note that if these have -/// calls from *monomorphic* code, this will make the Hugr invalid (call [monomorphize] -/// first). -/// -/// TODO replace this with a more general remove-unused-functions pass -/// -pub fn remove_polyfuncs(mut h: Hugr) -> Hugr { - let mut pfs_to_delete = Vec::new(); - let mut to_scan = Vec::from_iter(h.children(h.root())); - while let Some(n) = to_scan.pop() { - if is_polymorphic_funcdefn(h.get_optype(n)) { - pfs_to_delete.push(n) - } else { - to_scan.extend(h.children(n)); - } - } - for n in pfs_to_delete { - h.remove_subtree(n); - } - h -} - fn is_polymorphic(fd: &FuncDefn) -> bool { !fd.signature.params().is_empty() } @@ -322,7 +302,9 @@ mod test { use hugr_core::{Hugr, HugrView, Node}; use rstest::rstest; - use super::{is_polymorphic, mangle_inner_func, mangle_name, monomorphize, remove_polyfuncs}; + use crate::call_graph::remove_dead_funcs; + + use super::{is_polymorphic, mangle_inner_func, mangle_name, monomorphize}; fn pair_type(ty: Type) -> Type { Type::new_tuple(vec![ty.clone(), ty]) @@ -426,7 +408,8 @@ mod test { assert_eq!(monomorphize(mono.clone()), mono); // Idempotent - let nopoly = remove_polyfuncs(mono); + let mut nopoly = mono; + remove_dead_funcs(&mut nopoly, []); let mut funcs = list_funcs(&nopoly); assert!(funcs.values().all(|(_, fd)| !is_polymorphic(fd))); @@ -645,7 +628,8 @@ mod test { module_builder.finish_hugr().unwrap() }; - let mono_hugr = remove_polyfuncs(monomorphize(hugr)); + let mut mono_hugr = monomorphize(hugr); + remove_dead_funcs(&mut mono_hugr, []); let funcs = list_funcs(&mono_hugr); assert!(funcs.values().all(|(_, fd)| !is_polymorphic(fd))); From c3dd939b6d07e96cd1b33d778b8d2d458a7168b1 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 12:42:17 +0000 Subject: [PATCH 04/22] Warn on missing docs --- hugr-passes/src/call_graph.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index ca8dd89ca..888f6b67e 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -1,3 +1,4 @@ +#![warn(missing_docs)] //! Data structure for call graphs of a Hugr, and some transformations using them. use std::collections::{HashMap, HashSet}; @@ -37,10 +38,10 @@ impl CallGraph { /// Calls to functions outside the view will be dropped. pub fn new(hugr: &impl HugrView) -> Self { let mut g = Graph::default(); + // For non-Module-rooted Hugrs, make sure we include the root let root = (!hugr.get_optype(hugr.root()).is_module()).then_some(hugr.root()); let node_to_g = hugr .nodes() - // For non-Module-rooted Hugrs, make sure we include the root .filter(|&n| Some(n) == root || OpTag::Function.is_superset(hugr.get_optype(n).tag())) .map(|n| (n, g.add_node(n))) .collect::>(); From 3bc33bcaef9c027aff06211e3ded25314980cb4e Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 17:38:16 +0000 Subject: [PATCH 05/22] Reinstate remove_polyfuncs but deprecate: guess next version number, and suppress warning on re-export --- hugr-passes/src/lib.rs | 2 ++ hugr-passes/src/monomorphize.rs | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index 5da8c68a2..b92a4f04b 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -9,6 +9,8 @@ pub mod lower; pub mod merge_bbs; mod monomorphize; pub use monomorphize::monomorphize; +#[allow(deprecated)] +pub use monomorphize::remove_polyfuncs; pub mod nest_cfgs; pub mod non_local; pub mod validation; diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index 1bb9185ef..6413fe1ee 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -52,6 +52,31 @@ pub fn monomorphize(mut h: Hugr) -> Hugr { h } +/// Removes any polymorphic [FuncDefn]s from the Hugr. Note that if these have +/// calls from *monomorphic* code, this will make the Hugr invalid (call [monomorphize] +/// first). +/// +/// Deprecated: use [remove_dead_funcs] instead. +#[deprecated( + since = "0.14.1", + note = "Use hugr_passes::call_graph::remove_dead_funcs instead" +)] +pub fn remove_polyfuncs(mut h: Hugr) -> Hugr { + let mut pfs_to_delete = Vec::new(); + let mut to_scan = Vec::from_iter(h.children(h.root())); + while let Some(n) = to_scan.pop() { + if is_polymorphic_funcdefn(h.get_optype(n)) { + pfs_to_delete.push(n) + } else { + to_scan.extend(h.children(n)); + } + } + for n in pfs_to_delete { + h.remove_subtree(n); + } + h +} + fn is_polymorphic(fd: &FuncDefn) -> bool { !fd.signature.params().is_empty() } From 1e95bc6331c874a795c98914257765ecf3566021 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 18:29:50 +0000 Subject: [PATCH 06/22] Test module entry_points --- hugr-passes/src/call_graph.rs | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index 888f6b67e..c916abe3a 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -133,3 +133,72 @@ pub fn remove_dead_funcs(h: &mut impl HugrMut, entry_points: impl IntoIterator, + #[case] retained_funcs: Vec<&'static str>, + ) -> Result<(), Box> { + let mut hb = ModuleBuilder::new(); + let o2 = hb.define_function("other2", Signature::new_endo(usize_t()))?; + let o2inp = o2.input_wires(); + let o2 = o2.finish_with_outputs(o2inp)?; + let mut o1 = hb.define_function("other1", Signature::new_endo(usize_t()))?; + + let o1c = o1.call(o2.handle(), &[], o1.input_wires())?; + o1.finish_with_outputs(o1c.outputs())?; + + let fm = hb.define_function("from_main", Signature::new_endo(usize_t()))?; + let f_inp = fm.input_wires(); + let fm = fm.finish_with_outputs(f_inp)?; + let mut m = hb.define_function("main", Signature::new_endo(usize_t()))?; + let mc = m.call(fm.handle(), &[], m.input_wires())?; + m.finish_with_outputs(mc.outputs())?; + + let mut hugr = hb.finish_hugr()?; + + let avail_funcs = hugr + .nodes() + .filter_map(|n| { + hugr.get_optype(n) + .as_func_defn() + .map(|fd| (fd.name.clone(), n)) + }) + .collect::>(); + + remove_dead_funcs( + &mut hugr, + entry_points + .into_iter() + .map(|name| *avail_funcs.get(name).unwrap()) + .collect::>(), + ); + let remaining_funcs = hugr + .nodes() + .filter_map(|n| hugr.get_optype(n).as_func_defn().map(|fd| fd.name.as_str())) + .sorted() + .collect_vec(); + assert_eq!(remaining_funcs, retained_funcs); + Ok(()) + } +} From 9061dc9094e5a5bfbc523fe4907de2ce85a4822a Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 18:42:06 +0000 Subject: [PATCH 07/22] Move reachable_funcs outside of CallGraph --- hugr-passes/src/call_graph.rs | 67 +++++++++++++++++------------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index c916abe3a..ede5ff9a2 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -72,39 +72,39 @@ impl CallGraph { } CallGraph { g, node_to_g } } +} - fn reachable_funcs( - &self, - h: &impl HugrView, - roots: impl IntoIterator, - ) -> impl Iterator + '_ { - let mut entry_points = roots.into_iter().collect_vec(); - let mut b = if h.get_optype(h.root()).is_module() { - if entry_points.is_empty() { - entry_points.push( - h.children(h.root()) - .filter(|n| { - h.get_optype(*n) - .as_func_defn() - .is_some_and(|fd| fd.name == "main") - }) - .exactly_one() - .ok() - .expect("No entry_points provided, Module must contain `main`"), - ); - } - let mut entry_points = entry_points - .into_iter() - .map(|i| self.node_to_g.get(&i).unwrap()); - let mut b = Bfs::new(&self.g, *entry_points.next().unwrap()); - b.stack.extend(entry_points); - b - } else { - assert!(entry_points.is_empty()); - Bfs::new(&self.g, *self.node_to_g.get(&h.root()).unwrap()) - }; - std::iter::from_fn(move || b.next(&self.g)).map(|i| *self.g.node_weight(i).unwrap()) - } +fn reachable_funcs<'a>( + cg: &'a CallGraph, + h: &impl HugrView, + roots: impl IntoIterator, +) -> impl Iterator + 'a { + let mut entry_points = roots.into_iter().collect_vec(); + let mut b = if h.get_optype(h.root()).is_module() { + if entry_points.is_empty() { + entry_points.push( + h.children(h.root()) + .filter(|n| { + h.get_optype(*n) + .as_func_defn() + .is_some_and(|fd| fd.name == "main") + }) + .exactly_one() + .ok() + .expect("No entry_points provided, Module must contain `main`"), + ); + } + let mut entry_points = entry_points + .into_iter() + .map(|i| cg.node_to_g.get(&i).unwrap()); + let mut b = Bfs::new(&cg.g, *entry_points.next().unwrap()); + b.stack.extend(entry_points); + b + } else { + assert!(entry_points.is_empty()); + Bfs::new(&cg.g, *cg.node_to_g.get(&h.root()).unwrap()) + }; + std::iter::from_fn(move || b.next(&cg.g)).map(|i| *cg.g.node_weight(i).unwrap()) } /// Delete from the Hugr any functions that are not used by either [Call](OpType::Call) or @@ -123,8 +123,7 @@ impl CallGraph { /// * If the Hugr is Module-rooted, and `entry_points` is non-empty but contains nodes that /// are not [FuncDefn](OpType::FuncDefn)s pub fn remove_dead_funcs(h: &mut impl HugrMut, entry_points: impl IntoIterator) { - let cg = CallGraph::new(h); - let reachable = cg.reachable_funcs(h, entry_points).collect::>(); + let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points).collect::>(); let unreachable = h .nodes() .filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n)) From e29ffa21a97220516bca4a990cf3844536320b9c Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Tue, 17 Dec 2024 18:48:50 +0000 Subject: [PATCH 08/22] Rename entry_points<->roots, use extend + assert --- hugr-passes/src/call_graph.rs | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index ede5ff9a2..57a7c9aeb 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -77,31 +77,24 @@ impl CallGraph { fn reachable_funcs<'a>( cg: &'a CallGraph, h: &impl HugrView, - roots: impl IntoIterator, + entry_points: impl IntoIterator, ) -> impl Iterator + 'a { - let mut entry_points = roots.into_iter().collect_vec(); + let mut roots = entry_points.into_iter().collect_vec(); let mut b = if h.get_optype(h.root()).is_module() { - if entry_points.is_empty() { - entry_points.push( - h.children(h.root()) - .filter(|n| { - h.get_optype(*n) - .as_func_defn() - .is_some_and(|fd| fd.name == "main") - }) - .exactly_one() - .ok() - .expect("No entry_points provided, Module must contain `main`"), - ); + if roots.is_empty() { + roots.extend(h.children(h.root()).filter(|n| { + h.get_optype(*n) + .as_func_defn() + .is_some_and(|fd| fd.name == "main") + })); + assert_eq!(roots.len(), 1, "No entry_points for Module and no `main`"); } - let mut entry_points = entry_points - .into_iter() - .map(|i| cg.node_to_g.get(&i).unwrap()); - let mut b = Bfs::new(&cg.g, *entry_points.next().unwrap()); - b.stack.extend(entry_points); + let mut roots = roots.into_iter().map(|i| cg.node_to_g.get(&i).unwrap()); + let mut b = Bfs::new(&cg.g, *roots.next().unwrap()); + b.stack.extend(roots); b } else { - assert!(entry_points.is_empty()); + assert!(roots.is_empty()); Bfs::new(&cg.g, *cg.node_to_g.get(&h.root()).unwrap()) }; std::iter::from_fn(move || b.next(&cg.g)).map(|i| *cg.g.node_weight(i).unwrap()) From 220bf6775dc427a4e4a99ba0577b335f536c3413 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Wed, 18 Dec 2024 16:02:14 +0000 Subject: [PATCH 09/22] Add RemoveDeadFuncsPass. TODO make remove_dead_funcs use ValidationLevel::default --- hugr-passes/src/call_graph.rs | 35 +++++++++++++++++++++++++++++++++++ hugr-passes/src/lib.rs | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index 57a7c9aeb..244ae9ad9 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -10,6 +10,8 @@ use hugr_core::{ use itertools::Itertools; use petgraph::{graph::NodeIndex, visit::Bfs, Graph}; +use crate::validation::{ValidatePassError, ValidationLevel}; + /// Weight for an edge in a [CallGraph] pub enum CallGraphEdge { /// Edge corresponds to a [Call](OpType::Call) node (specified) in the Hugr @@ -100,6 +102,39 @@ fn reachable_funcs<'a>( std::iter::from_fn(move || b.next(&cg.g)).map(|i| *cg.g.node_weight(i).unwrap()) } +#[derive(Debug, Clone, Default)] +/// A configuration for the Dead Function Removal pass. +pub struct RemoveDeadFuncsPass { + validation: ValidationLevel, + entry_points: Vec, +} + +impl RemoveDeadFuncsPass { + /// Sets the validation level used before and after the pass is run + pub fn validation_level(mut self, level: ValidationLevel) -> Self { + self.validation = level; + self + } + + /// Adds new entry points for a Module-rooted Hugr, + /// i.e., FuncDefns that are children of the root. + pub fn with_module_entry_points( + mut self, + entry_points: impl IntoIterator, + ) -> Self { + self.entry_points.extend(entry_points); + self + } + + /// Runs the pass (see [remove_dead_funcs]) with this configuration + pub fn run(&self, hugr: &mut H) -> Result<(), ValidatePassError> { + self.validation.run_validated_pass(hugr, |hugr: &mut H, _| { + remove_dead_funcs(hugr, self.entry_points.iter().cloned()); + Ok(()) + }) + } +} + /// Delete from the Hugr any functions that are not used by either [Call](OpType::Call) or /// [LoadFunction](OpType::LoadFunction) nodes in reachable parts. /// diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index 36240f569..d3d59866a 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -11,7 +11,7 @@ mod monomorphize; // TODO: Deprecated re-export. Remove on a breaking release. #[deprecated( since = "0.14.1", - note = "Use `hugr::algorithms::MonomorphizePass` instead." + note = "Use `hugr::algorithms::call_graph::RemoveDeadFuncsPass` instead." )] #[allow(deprecated)] pub use monomorphize::remove_polyfuncs; From 466123d5ecc8f633c579d76b7f9d6a4e236d2093 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Wed, 18 Dec 2024 16:06:10 +0000 Subject: [PATCH 10/22] enclosing{=>_func}, switch order, comment --- hugr-passes/src/call_graph.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index 244ae9ad9..978dc247e 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -48,12 +48,12 @@ impl CallGraph { .map(|n| (n, g.add_node(n))) .collect::>(); for (func, cg_node) in node_to_g.iter() { - traverse(hugr, *func, *cg_node, &mut g, &node_to_g) + traverse(hugr, *cg_node, *func, &mut g, &node_to_g) } fn traverse( h: &impl HugrView, - node: Node, - enclosing: NodeIndex, + enclosing_func: NodeIndex, + node: Node, // Nonstrict-descendant of `enclosing_func`` g: &mut Graph, node_to_g: &HashMap>, ) { @@ -61,14 +61,14 @@ impl CallGraph { if h.get_optype(ch).is_func_defn() { continue; }; - traverse(h, ch, enclosing, g, node_to_g); + traverse(h, enclosing_func, ch, g, node_to_g); let weight = match h.get_optype(ch) { OpType::Call(_) => CallGraphEdge::Call(ch), OpType::LoadFunction(_) => CallGraphEdge::LoadFunction(ch), _ => continue, }; if let Some(target) = h.static_source(ch) { - g.add_edge(enclosing, *node_to_g.get(&target).unwrap(), weight); + g.add_edge(enclosing_func, *node_to_g.get(&target).unwrap(), weight); } } } From f8008d9428edeeb035682ccdee63faf873fae40a Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Wed, 18 Dec 2024 17:48:49 +0000 Subject: [PATCH 11/22] Use Pass in tests --- hugr-passes/src/call_graph.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index 978dc247e..1d6196037 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -173,7 +173,7 @@ mod test { }; use hugr_core::{extension::prelude::usize_t, types::Signature, HugrView}; - use super::remove_dead_funcs; + use super::RemoveDeadFuncsPass; #[rstest] #[case([], vec!["from_main", "main"])] @@ -213,13 +213,16 @@ mod test { }) .collect::>(); - remove_dead_funcs( - &mut hugr, - entry_points - .into_iter() - .map(|name| *avail_funcs.get(name).unwrap()) - .collect::>(), - ); + RemoveDeadFuncsPass::default() + .with_module_entry_points( + entry_points + .into_iter() + .map(|name| *avail_funcs.get(name).unwrap()) + .collect::>(), + ) + .run(&mut hugr) + .unwrap(); + let remaining_funcs = hugr .nodes() .filter_map(|n| hugr.get_optype(n).as_func_defn().map(|fd| fd.name.as_str())) From 7ba818d8be2e6299f391997885ce0a4bd911fae3 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Fri, 20 Dec 2024 14:33:13 +0000 Subject: [PATCH 12/22] Add CallGraphNode enum and accessors --- hugr-passes/src/call_graph.rs | 56 ++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index 1d6196037..28dac6e14 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -2,11 +2,7 @@ //! Data structure for call graphs of a Hugr, and some transformations using them. use std::collections::{HashMap, HashSet}; -use hugr_core::{ - hugr::hugrmut::HugrMut, - ops::{OpTag, OpTrait, OpType}, - HugrView, Node, -}; +use hugr_core::{hugr::hugrmut::HugrMut, ops::OpType, HugrView, Node}; use itertools::Itertools; use petgraph::{graph::NodeIndex, visit::Bfs, Graph}; @@ -20,18 +16,30 @@ pub enum CallGraphEdge { LoadFunction(Node), } +/// Weight for a petgraph-node in a [CallGraph] +pub enum CallGraphNode { + /// petgraph-node corresponds to a [FuncDecl](OpType::FuncDecl) node (specified) in the Hugr + FuncDecl(Node), + /// petgraph-node corresponds to a [FuncDefn](OpType::FuncDefn) node (specified) in the Hugr + FuncDefn(Node), + /// petgraph-node corresponds to the root node of the hugr, 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 outgoing edges, so is not represented in the petgraph. + NonFuncRoot, +} + /// Details the [Call]s and [LoadFunction]s in a Hugr. /// Each node in the `CallGraph` corresponds to a [FuncDefn] in the Hugr; each edge corresponds /// to a [Call]/[LoadFunction] of the edge's target, contained in the edge's source. /// /// For Hugrs whose root is neither a [Module](OpType::Module) nor a [FuncDefn], the call graph -/// will have an additional node corresponding to the Hugr's root, with no incoming edges. +/// will have an additional [CallGraphNode::NonFuncRoot] corresponding to the Hugr's root, with no incoming edges. /// /// [Call]: OpType::Call /// [FuncDefn]: OpType::FuncDefn /// [LoadFunction]: OpType::LoadFunction pub struct CallGraph { - g: Graph, + g: Graph, node_to_g: HashMap>, } @@ -40,12 +48,17 @@ impl CallGraph { /// Calls to functions outside the view will be dropped. pub fn new(hugr: &impl HugrView) -> Self { let mut g = Graph::default(); - // For non-Module-rooted Hugrs, make sure we include the root - let root = (!hugr.get_optype(hugr.root()).is_module()).then_some(hugr.root()); + let non_func_root = (!hugr.get_optype(hugr.root()).is_module()).then_some(hugr.root()); let node_to_g = hugr .nodes() - .filter(|&n| Some(n) == root || OpTag::Function.is_superset(hugr.get_optype(n).tag())) - .map(|n| (n, g.add_node(n))) + .filter_map(|n| { + let weight = match hugr.get_optype(n) { + OpType::FuncDecl(_) => CallGraphNode::FuncDecl(n), + OpType::FuncDefn(_) => CallGraphNode::FuncDefn(n), + _ => (Some(n) == non_func_root).then_some(CallGraphNode::NonFuncRoot)?, + }; + Some((n, g.add_node(weight))) + }) .collect::>(); for (func, cg_node) in node_to_g.iter() { traverse(hugr, *cg_node, *func, &mut g, &node_to_g) @@ -54,7 +67,7 @@ impl CallGraph { h: &impl HugrView, enclosing_func: NodeIndex, node: Node, // Nonstrict-descendant of `enclosing_func`` - g: &mut Graph, + g: &mut Graph, node_to_g: &HashMap>, ) { for ch in h.children(node) { @@ -74,11 +87,23 @@ impl CallGraph { } CallGraph { g, node_to_g } } + + /// Allows access to the petgraph + pub fn graph(&self) -> &Graph { + &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 hugr root. + pub fn node_index(&self, n: Node) -> Option> { + self.node_to_g.get(&n).copied() + } } fn reachable_funcs<'a>( cg: &'a CallGraph, - h: &impl HugrView, + h: &'a impl HugrView, entry_points: impl IntoIterator, ) -> impl Iterator + 'a { let mut roots = entry_points.into_iter().collect_vec(); @@ -99,7 +124,10 @@ fn reachable_funcs<'a>( assert!(roots.is_empty()); Bfs::new(&cg.g, *cg.node_to_g.get(&h.root()).unwrap()) }; - std::iter::from_fn(move || b.next(&cg.g)).map(|i| *cg.g.node_weight(i).unwrap()) + std::iter::from_fn(move || b.next(&cg.g)).map(|i| match cg.g.node_weight(i).unwrap() { + CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, + CallGraphNode::NonFuncRoot => h.root(), + }) } #[derive(Debug, Clone, Default)] From 03cac7888c143f340814a669495cdccc2a824456 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Fri, 20 Dec 2024 14:44:27 +0000 Subject: [PATCH 13/22] Move remove_dead_funcs stuff into separate file --- hugr-passes/src/call_graph.rs | 171 +------------------------ hugr-passes/src/dead_func_removal.rs | 180 +++++++++++++++++++++++++++ hugr-passes/src/lib.rs | 2 + hugr-passes/src/monomorphize.rs | 4 +- 4 files changed, 188 insertions(+), 169 deletions(-) create mode 100644 hugr-passes/src/dead_func_removal.rs diff --git a/hugr-passes/src/call_graph.rs b/hugr-passes/src/call_graph.rs index 28dac6e14..a4ef11516 100644 --- a/hugr-passes/src/call_graph.rs +++ b/hugr-passes/src/call_graph.rs @@ -1,12 +1,9 @@ #![warn(missing_docs)] -//! Data structure for call graphs of a Hugr, and some transformations using them. -use std::collections::{HashMap, HashSet}; +//! Data structure for call graphs of a Hugr +use std::collections::HashMap; -use hugr_core::{hugr::hugrmut::HugrMut, ops::OpType, HugrView, Node}; -use itertools::Itertools; -use petgraph::{graph::NodeIndex, visit::Bfs, Graph}; - -use crate::validation::{ValidatePassError, ValidationLevel}; +use hugr_core::{ops::OpType, HugrView, Node}; +use petgraph::{graph::NodeIndex, Graph}; /// Weight for an edge in a [CallGraph] pub enum CallGraphEdge { @@ -100,163 +97,3 @@ impl CallGraph { self.node_to_g.get(&n).copied() } } - -fn reachable_funcs<'a>( - cg: &'a CallGraph, - h: &'a impl HugrView, - entry_points: impl IntoIterator, -) -> impl Iterator + 'a { - let mut roots = entry_points.into_iter().collect_vec(); - let mut b = if h.get_optype(h.root()).is_module() { - if roots.is_empty() { - roots.extend(h.children(h.root()).filter(|n| { - h.get_optype(*n) - .as_func_defn() - .is_some_and(|fd| fd.name == "main") - })); - assert_eq!(roots.len(), 1, "No entry_points for Module and no `main`"); - } - let mut roots = roots.into_iter().map(|i| cg.node_to_g.get(&i).unwrap()); - let mut b = Bfs::new(&cg.g, *roots.next().unwrap()); - b.stack.extend(roots); - b - } else { - assert!(roots.is_empty()); - Bfs::new(&cg.g, *cg.node_to_g.get(&h.root()).unwrap()) - }; - std::iter::from_fn(move || b.next(&cg.g)).map(|i| match cg.g.node_weight(i).unwrap() { - CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, - CallGraphNode::NonFuncRoot => h.root(), - }) -} - -#[derive(Debug, Clone, Default)] -/// A configuration for the Dead Function Removal pass. -pub struct RemoveDeadFuncsPass { - validation: ValidationLevel, - entry_points: Vec, -} - -impl RemoveDeadFuncsPass { - /// Sets the validation level used before and after the pass is run - pub fn validation_level(mut self, level: ValidationLevel) -> Self { - self.validation = level; - self - } - - /// Adds new entry points for a Module-rooted Hugr, - /// i.e., FuncDefns that are children of the root. - pub fn with_module_entry_points( - mut self, - entry_points: impl IntoIterator, - ) -> Self { - self.entry_points.extend(entry_points); - self - } - - /// Runs the pass (see [remove_dead_funcs]) with this configuration - pub fn run(&self, hugr: &mut H) -> Result<(), ValidatePassError> { - self.validation.run_validated_pass(hugr, |hugr: &mut H, _| { - remove_dead_funcs(hugr, self.entry_points.iter().cloned()); - Ok(()) - }) - } -} - -/// Delete from the Hugr any functions that are not used by either [Call](OpType::Call) or -/// [LoadFunction](OpType::LoadFunction) nodes in reachable parts. -/// -/// For [Module](OpType::Module)-rooted Hugrs, `roots` may provide a list of entry points; -/// these are expected to be children of the root although this is not enforced. If `roots` -/// is empty, then the root must have exactly one child being a function called `main`, -/// which is used as sole entry point. -/// -/// For non-Module-rooted Hugrs, `entry_points` must be empty; the root node is used. -/// -/// # Panics -/// * If the Hugr is non-Module-rooted and `entry_points` is non-empty -/// * If the Hugr is Module-rooted, but does not declare `main`, and `entry_points` is empty -/// * If the Hugr is Module-rooted, and `entry_points` is non-empty but contains nodes that -/// are not [FuncDefn](OpType::FuncDefn)s -pub fn remove_dead_funcs(h: &mut impl HugrMut, entry_points: impl IntoIterator) { - let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points).collect::>(); - let unreachable = h - .nodes() - .filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n)) - .collect::>(); - for n in unreachable { - h.remove_subtree(n); - } -} - -#[cfg(test)] -mod test { - use std::collections::HashMap; - - use itertools::Itertools; - use rstest::rstest; - - use hugr_core::builder::{ - Container, Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder, - }; - use hugr_core::{extension::prelude::usize_t, types::Signature, HugrView}; - - use super::RemoveDeadFuncsPass; - - #[rstest] - #[case([], vec!["from_main", "main"])] - #[case(["main"], vec!["from_main", "main"])] - #[case(["from_main"], vec!["from_main"])] - #[case(["other1"], vec!["other1", "other2"])] - #[case(["other2"], vec!["other2"])] - #[case(["other1", "other2"], vec!["other1", "other2"])] - fn remove_dead_funcs_entry_points( - #[case] entry_points: impl IntoIterator, - #[case] retained_funcs: Vec<&'static str>, - ) -> Result<(), Box> { - let mut hb = ModuleBuilder::new(); - let o2 = hb.define_function("other2", Signature::new_endo(usize_t()))?; - let o2inp = o2.input_wires(); - let o2 = o2.finish_with_outputs(o2inp)?; - let mut o1 = hb.define_function("other1", Signature::new_endo(usize_t()))?; - - let o1c = o1.call(o2.handle(), &[], o1.input_wires())?; - o1.finish_with_outputs(o1c.outputs())?; - - let fm = hb.define_function("from_main", Signature::new_endo(usize_t()))?; - let f_inp = fm.input_wires(); - let fm = fm.finish_with_outputs(f_inp)?; - let mut m = hb.define_function("main", Signature::new_endo(usize_t()))?; - let mc = m.call(fm.handle(), &[], m.input_wires())?; - m.finish_with_outputs(mc.outputs())?; - - let mut hugr = hb.finish_hugr()?; - - let avail_funcs = hugr - .nodes() - .filter_map(|n| { - hugr.get_optype(n) - .as_func_defn() - .map(|fd| (fd.name.clone(), n)) - }) - .collect::>(); - - RemoveDeadFuncsPass::default() - .with_module_entry_points( - entry_points - .into_iter() - .map(|name| *avail_funcs.get(name).unwrap()) - .collect::>(), - ) - .run(&mut hugr) - .unwrap(); - - let remaining_funcs = hugr - .nodes() - .filter_map(|n| hugr.get_optype(n).as_func_defn().map(|fd| fd.name.as_str())) - .sorted() - .collect_vec(); - assert_eq!(remaining_funcs, retained_funcs); - Ok(()) - } -} diff --git a/hugr-passes/src/dead_func_removal.rs b/hugr-passes/src/dead_func_removal.rs new file mode 100644 index 000000000..bf74107ca --- /dev/null +++ b/hugr-passes/src/dead_func_removal.rs @@ -0,0 +1,180 @@ +#![warn(missing_docs)] +//! Pass for removing statically-unreachable functions from a Hugr + +use std::collections::HashSet; + +use hugr_core::{hugr::hugrmut::HugrMut, HugrView, Node}; +use itertools::Itertools; +use petgraph::visit::Bfs; + +use crate::validation::{ValidatePassError, ValidationLevel}; + +use super::call_graph::{CallGraph, CallGraphNode}; + +fn reachable_funcs<'a>( + cg: &'a CallGraph, + h: &'a impl HugrView, + entry_points: impl IntoIterator, +) -> impl Iterator + 'a { + let mut roots = entry_points.into_iter().collect_vec(); + let mut b = if h.get_optype(h.root()).is_module() { + if roots.is_empty() { + roots.extend(h.children(h.root()).filter(|n| { + h.get_optype(*n) + .as_func_defn() + .is_some_and(|fd| fd.name == "main") + })); + assert_eq!(roots.len(), 1, "No entry_points for Module and no `main`"); + } + let mut roots = roots.into_iter().map(|i| cg.node_index(i).unwrap()); + let mut b = Bfs::new(cg.graph(), roots.next().unwrap()); + b.stack.extend(roots); + b + } else { + assert!(roots.is_empty()); + Bfs::new(&cg.graph(), cg.node_index(h.root()).unwrap()) + }; + let g = cg.graph(); + std::iter::from_fn(move || { + b.next(g).map(|i| match g.node_weight(i).unwrap() { + CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, + CallGraphNode::NonFuncRoot => h.root(), + }) + }) +} + +#[derive(Debug, Clone, Default)] +/// A configuration for the Dead Function Removal pass. +pub struct RemoveDeadFuncsPass { + validation: ValidationLevel, + entry_points: Vec, +} + +impl RemoveDeadFuncsPass { + /// Sets the validation level used before and after the pass is run + pub fn validation_level(mut self, level: ValidationLevel) -> Self { + self.validation = level; + self + } + + /// Adds new entry points for a Module-rooted Hugr, + /// i.e., FuncDefns that are children of the root. + pub fn with_module_entry_points( + mut self, + entry_points: impl IntoIterator, + ) -> Self { + self.entry_points.extend(entry_points); + self + } + + /// Runs the pass (see [remove_dead_funcs]) with this configuration + pub fn run(&self, hugr: &mut H) -> Result<(), ValidatePassError> { + self.validation.run_validated_pass(hugr, |hugr: &mut H, _| { + remove_dead_funcs(hugr, self.entry_points.iter().cloned()); + Ok(()) + }) + } +} + +/// Delete from the Hugr any functions that are not used by either [Call] or +/// [LoadFunction] nodes in reachable parts. +/// +/// For [Module]-rooted Hugrs, `roots` may provide a list of entry points; +/// these are expected to be children of the root although this is not enforced. If `roots` +/// is empty, then the root must have exactly one child being a function called `main`, +/// which is used as sole entry point. +/// +/// For non-Module-rooted Hugrs, `entry_points` must be empty; the root node is used. +/// +/// # Panics +/// * If the Hugr is non-Module-rooted and `entry_points` is non-empty +/// * If the Hugr is Module-rooted, but does not declare `main`, and `entry_points` is empty +/// * If the Hugr is Module-rooted, and `entry_points` is non-empty but contains nodes that +/// are not [FuncDefn]s +/// +/// [Call]: hugr_core::ops::OpType::Call +/// [FuncDefn]: hugr_core::ops::OpType::FuncDefn +/// [LoadFunction]: hugr_core::ops::OpType::LoadFunction +/// [Module]: hugr_core::ops::OpType::Module +pub fn remove_dead_funcs(h: &mut impl HugrMut, entry_points: impl IntoIterator) { + let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points).collect::>(); + let unreachable = h + .nodes() + .filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n)) + .collect::>(); + for n in unreachable { + h.remove_subtree(n); + } +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use itertools::Itertools; + use rstest::rstest; + + use hugr_core::builder::{ + Container, Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder, + }; + use hugr_core::{extension::prelude::usize_t, types::Signature, HugrView}; + + use super::RemoveDeadFuncsPass; + + #[rstest] + #[case([], vec!["from_main", "main"])] + #[case(["main"], vec!["from_main", "main"])] + #[case(["from_main"], vec!["from_main"])] + #[case(["other1"], vec!["other1", "other2"])] + #[case(["other2"], vec!["other2"])] + #[case(["other1", "other2"], vec!["other1", "other2"])] + fn remove_dead_funcs_entry_points( + #[case] entry_points: impl IntoIterator, + #[case] retained_funcs: Vec<&'static str>, + ) -> Result<(), Box> { + let mut hb = ModuleBuilder::new(); + let o2 = hb.define_function("other2", Signature::new_endo(usize_t()))?; + let o2inp = o2.input_wires(); + let o2 = o2.finish_with_outputs(o2inp)?; + let mut o1 = hb.define_function("other1", Signature::new_endo(usize_t()))?; + + let o1c = o1.call(o2.handle(), &[], o1.input_wires())?; + o1.finish_with_outputs(o1c.outputs())?; + + let fm = hb.define_function("from_main", Signature::new_endo(usize_t()))?; + let f_inp = fm.input_wires(); + let fm = fm.finish_with_outputs(f_inp)?; + let mut m = hb.define_function("main", Signature::new_endo(usize_t()))?; + let mc = m.call(fm.handle(), &[], m.input_wires())?; + m.finish_with_outputs(mc.outputs())?; + + let mut hugr = hb.finish_hugr()?; + + let avail_funcs = hugr + .nodes() + .filter_map(|n| { + hugr.get_optype(n) + .as_func_defn() + .map(|fd| (fd.name.clone(), n)) + }) + .collect::>(); + + RemoveDeadFuncsPass::default() + .with_module_entry_points( + entry_points + .into_iter() + .map(|name| *avail_funcs.get(name).unwrap()) + .collect::>(), + ) + .run(&mut hugr) + .unwrap(); + + let remaining_funcs = hugr + .nodes() + .filter_map(|n| hugr.get_optype(n).as_func_defn().map(|fd| fd.name.as_str())) + .sorted() + .collect_vec(); + assert_eq!(remaining_funcs, retained_funcs); + Ok(()) + } +} diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index d3d59866a..eb183731e 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -3,6 +3,8 @@ pub mod call_graph; pub mod const_fold; pub mod dataflow; +mod dead_func_removal; +pub use dead_func_removal::{remove_dead_funcs, RemoveDeadFuncsPass}; pub mod force_order; mod half_node; pub mod lower; diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index b70e2204b..52ce65981 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -14,7 +14,7 @@ use hugr_core::hugr::{hugrmut::HugrMut, Hugr, HugrView, OpType}; use itertools::Itertools as _; use thiserror::Error; -use crate::call_graph::remove_dead_funcs; +use crate::remove_dead_funcs; /// Replaces calls to polymorphic functions with calls to new monomorphic /// instantiations of the polymorphic ones. @@ -382,7 +382,7 @@ mod test { use hugr_core::{Hugr, HugrView, Node}; use rstest::rstest; - use crate::call_graph::remove_dead_funcs; + use crate::remove_dead_funcs; use super::{is_polymorphic, mangle_inner_func, mangle_name, MonomorphizePass}; From e39c2794578d1865c1bbdfa91e3a0008bb6c37e4 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Fri, 20 Dec 2024 15:27:13 +0000 Subject: [PATCH 14/22] Add (rather useless atm) error type --- hugr-passes/src/dead_func_removal.rs | 20 ++++++++++++++++---- hugr-passes/src/monomorphize.rs | 6 +++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/hugr-passes/src/dead_func_removal.rs b/hugr-passes/src/dead_func_removal.rs index bf74107ca..edf07c5e7 100644 --- a/hugr-passes/src/dead_func_removal.rs +++ b/hugr-passes/src/dead_func_removal.rs @@ -11,6 +11,15 @@ use crate::validation::{ValidatePassError, ValidationLevel}; use super::call_graph::{CallGraph, CallGraphNode}; +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +/// Errors produced by [ConstantFoldPass]. +pub enum RemoveDeadFuncsError { + #[error(transparent)] + #[allow(missing_docs)] + ValidationError(#[from] ValidatePassError), +} + fn reachable_funcs<'a>( cg: &'a CallGraph, h: &'a impl HugrView, @@ -68,10 +77,9 @@ impl RemoveDeadFuncsPass { } /// Runs the pass (see [remove_dead_funcs]) with this configuration - pub fn run(&self, hugr: &mut H) -> Result<(), ValidatePassError> { + pub fn run(&self, hugr: &mut H) -> Result<(), RemoveDeadFuncsError> { self.validation.run_validated_pass(hugr, |hugr: &mut H, _| { - remove_dead_funcs(hugr, self.entry_points.iter().cloned()); - Ok(()) + remove_dead_funcs(hugr, self.entry_points.iter().cloned()) }) } } @@ -96,7 +104,10 @@ impl RemoveDeadFuncsPass { /// [FuncDefn]: hugr_core::ops::OpType::FuncDefn /// [LoadFunction]: hugr_core::ops::OpType::LoadFunction /// [Module]: hugr_core::ops::OpType::Module -pub fn remove_dead_funcs(h: &mut impl HugrMut, entry_points: impl IntoIterator) { +pub fn remove_dead_funcs( + h: &mut impl HugrMut, + entry_points: impl IntoIterator, +) -> Result<(), RemoveDeadFuncsError> { let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points).collect::>(); let unreachable = h .nodes() @@ -105,6 +116,7 @@ pub fn remove_dead_funcs(h: &mut impl HugrMut, entry_points: impl IntoIterator Date: Fri, 20 Dec 2024 15:00:45 +0000 Subject: [PATCH 15/22] switch from Bfs to Dfs --- hugr-passes/src/dead_func_removal.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hugr-passes/src/dead_func_removal.rs b/hugr-passes/src/dead_func_removal.rs index edf07c5e7..65f927428 100644 --- a/hugr-passes/src/dead_func_removal.rs +++ b/hugr-passes/src/dead_func_removal.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; use hugr_core::{hugr::hugrmut::HugrMut, HugrView, Node}; use itertools::Itertools; -use petgraph::visit::Bfs; +use petgraph::visit::Dfs; use crate::validation::{ValidatePassError, ValidationLevel}; @@ -26,7 +26,7 @@ fn reachable_funcs<'a>( entry_points: impl IntoIterator, ) -> impl Iterator + 'a { let mut roots = entry_points.into_iter().collect_vec(); - let mut b = if h.get_optype(h.root()).is_module() { + let mut searcher = if h.get_optype(h.root()).is_module() { if roots.is_empty() { roots.extend(h.children(h.root()).filter(|n| { h.get_optype(*n) @@ -36,16 +36,16 @@ fn reachable_funcs<'a>( assert_eq!(roots.len(), 1, "No entry_points for Module and no `main`"); } let mut roots = roots.into_iter().map(|i| cg.node_index(i).unwrap()); - let mut b = Bfs::new(cg.graph(), roots.next().unwrap()); - b.stack.extend(roots); - b + let mut d = Dfs::new(cg.graph(), roots.next().unwrap()); + d.stack.extend(roots); + d } else { assert!(roots.is_empty()); - Bfs::new(&cg.graph(), cg.node_index(h.root()).unwrap()) + Dfs::new(&cg.graph(), cg.node_index(h.root()).unwrap()) }; let g = cg.graph(); std::iter::from_fn(move || { - b.next(g).map(|i| match g.node_weight(i).unwrap() { + searcher.next(g).map(|i| match g.node_weight(i).unwrap() { CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, CallGraphNode::NonFuncRoot => h.root(), }) From c47a99e936c950b225dd0941b2068c2db5c8771f Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Fri, 20 Dec 2024 15:30:19 +0000 Subject: [PATCH 16/22] Don't auto-insert 'main'; error not panic on bad entry-point --- hugr-passes/src/dead_func_removal.rs | 67 +++++++++++++++------------- hugr-passes/src/monomorphize.rs | 8 ++-- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/hugr-passes/src/dead_func_removal.rs b/hugr-passes/src/dead_func_removal.rs index 65f927428..9d8607bc0 100644 --- a/hugr-passes/src/dead_func_removal.rs +++ b/hugr-passes/src/dead_func_removal.rs @@ -4,7 +4,6 @@ use std::collections::HashSet; use hugr_core::{hugr::hugrmut::HugrMut, HugrView, Node}; -use itertools::Itertools; use petgraph::visit::Dfs; use crate::validation::{ValidatePassError, ValidationLevel}; @@ -15,6 +14,8 @@ use super::call_graph::{CallGraph, CallGraphNode}; #[non_exhaustive] /// Errors produced by [ConstantFoldPass]. pub enum RemoveDeadFuncsError { + #[error("Node {0} was not a FuncDefn child of the Module root")] + InvalidEntryPoint(Node), #[error(transparent)] #[allow(missing_docs)] ValidationError(#[from] ValidatePassError), @@ -24,32 +25,32 @@ fn reachable_funcs<'a>( cg: &'a CallGraph, h: &'a impl HugrView, entry_points: impl IntoIterator, -) -> impl Iterator + 'a { - let mut roots = entry_points.into_iter().collect_vec(); +) -> Result + 'a, RemoveDeadFuncsError> { + let g = cg.graph(); + let mut entry_points = entry_points.into_iter(); let mut searcher = if h.get_optype(h.root()).is_module() { - if roots.is_empty() { - roots.extend(h.children(h.root()).filter(|n| { - h.get_optype(*n) - .as_func_defn() - .is_some_and(|fd| fd.name == "main") - })); - assert_eq!(roots.len(), 1, "No entry_points for Module and no `main`"); + let mut d = Dfs::new(g, 0.into()); + d.stack.clear(); + for n in entry_points { + if !h.get_optype(n).is_func_defn() || h.get_parent(n) != Some(h.root()) { + return Err(RemoveDeadFuncsError::InvalidEntryPoint(n)); + } + d.stack.push(cg.node_index(n).unwrap()) } - let mut roots = roots.into_iter().map(|i| cg.node_index(i).unwrap()); - let mut d = Dfs::new(cg.graph(), roots.next().unwrap()); - d.stack.extend(roots); d } else { - assert!(roots.is_empty()); - Dfs::new(&cg.graph(), cg.node_index(h.root()).unwrap()) + if let Some(n) = entry_points.next() { + // Can't be a child of the module root as there isn't a module root! + return Err(RemoveDeadFuncsError::InvalidEntryPoint(n)); + } + Dfs::new(g, cg.node_index(h.root()).unwrap()) }; - let g = cg.graph(); - std::iter::from_fn(move || { + Ok(std::iter::from_fn(move || { searcher.next(g).map(|i| match g.node_weight(i).unwrap() { CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, CallGraphNode::NonFuncRoot => h.root(), }) - }) + })) } #[derive(Debug, Clone, Default)] @@ -66,8 +67,11 @@ impl RemoveDeadFuncsPass { self } - /// Adds new entry points for a Module-rooted Hugr, - /// i.e., FuncDefns that are children of the root. + /// Adds new entry points - these must be [FuncDefn] nodes + /// that are children of the [Module] at the root of the Hugr. + /// + /// [FuncDefn]: hugr_core::ops::OpType::FuncDefn + /// [Module]: hugr_core::ops::OpType::Module pub fn with_module_entry_points( mut self, entry_points: impl IntoIterator, @@ -87,18 +91,17 @@ impl RemoveDeadFuncsPass { /// Delete from the Hugr any functions that are not used by either [Call] or /// [LoadFunction] nodes in reachable parts. /// -/// For [Module]-rooted Hugrs, `roots` may provide a list of entry points; -/// these are expected to be children of the root although this is not enforced. If `roots` -/// is empty, then the root must have exactly one child being a function called `main`, -/// which is used as sole entry point. +/// For [Module]-rooted Hugrs, `entry_points` may provide a list of entry points, +/// which must be children of the root. Note that if `entry_points` is empty, this will +/// result in all functions in the module being removed. /// -/// For non-Module-rooted Hugrs, `entry_points` must be empty; the root node is used. +/// For non-[Module]-rooted Hugrs, `entry_points` must be empty; the root node is used. /// -/// # Panics -/// * If the Hugr is non-Module-rooted and `entry_points` is non-empty -/// * If the Hugr is Module-rooted, but does not declare `main`, and `entry_points` is empty -/// * If the Hugr is Module-rooted, and `entry_points` is non-empty but contains nodes that -/// are not [FuncDefn]s +/// # Errors +/// * If there are any `entry_points` but the root of the hugr is not a [Module] +/// * If any node in `entry_points` is +/// * not a [FuncDefn], or +/// * not a child of the root /// /// [Call]: hugr_core::ops::OpType::Call /// [FuncDefn]: hugr_core::ops::OpType::FuncDefn @@ -108,7 +111,7 @@ pub fn remove_dead_funcs( h: &mut impl HugrMut, entry_points: impl IntoIterator, ) -> Result<(), RemoveDeadFuncsError> { - let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points).collect::>(); + let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points)?.collect::>(); let unreachable = h .nodes() .filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n)) @@ -134,7 +137,7 @@ mod test { use super::RemoveDeadFuncsPass; #[rstest] - #[case([], vec!["from_main", "main"])] + #[case([], vec![])] // No entry_points removes everything! #[case(["main"], vec!["from_main", "main"])] #[case(["from_main"], vec!["from_main"])] #[case(["other1"], vec!["other1", "other2"])] diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index c2a7ea79e..092cabf45 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -448,7 +448,7 @@ mod test { let trip = fb.add_dataflow_op(tag, [elem1, elem2, elem])?; fb.finish_with_outputs(trip.outputs())? }; - { + let mn = { let outs = vec![triple_type(usize_t()), triple_type(pair_type(usize_t()))]; let mut fb = mb.define_function("main", prelusig(usize_t(), outs))?; let [elem] = fb.input_wires_arr(); @@ -458,8 +458,8 @@ mod test { let pair = fb.call(db.handle(), &[usize_t().into()], [elem])?; let pty = pair_type(usize_t()).into(); let [res2] = fb.call(tr.handle(), &[pty], pair.outputs())?.outputs_arr(); - fb.finish_with_outputs([res1, res2])?; - } + fb.finish_with_outputs([res1, res2])? + }; let mut hugr = mb.finish_hugr()?; assert_eq!( hugr.nodes() @@ -493,7 +493,7 @@ mod test { assert_eq!(mono2, mono); // Idempotent let mut nopoly = mono; - remove_dead_funcs(&mut nopoly, [])?; + remove_dead_funcs(&mut nopoly, [mn.node()])?; let mut funcs = list_funcs(&nopoly); assert!(funcs.values().all(|(_, fd)| !is_polymorphic(fd))); From 4f36e565fae8575c89cb522b80d329c5a67090fa Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Fri, 20 Dec 2024 17:06:24 +0000 Subject: [PATCH 17/22] Sneakily-without-tests remove FuncDecls too --- hugr-passes/src/dead_func_removal.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hugr-passes/src/dead_func_removal.rs b/hugr-passes/src/dead_func_removal.rs index 9d8607bc0..7c0f7821f 100644 --- a/hugr-passes/src/dead_func_removal.rs +++ b/hugr-passes/src/dead_func_removal.rs @@ -3,7 +3,11 @@ use std::collections::HashSet; -use hugr_core::{hugr::hugrmut::HugrMut, HugrView, Node}; +use hugr_core::{ + hugr::hugrmut::HugrMut, + ops::{OpTag, OpTrait}, + HugrView, Node, +}; use petgraph::visit::Dfs; use crate::validation::{ValidatePassError, ValidationLevel}; @@ -114,7 +118,7 @@ pub fn remove_dead_funcs( let reachable = reachable_funcs(&CallGraph::new(h), h, entry_points)?.collect::>(); let unreachable = h .nodes() - .filter(|n| h.get_optype(*n).is_func_defn() && !reachable.contains(n)) + .filter(|n| OpTag::Function.is_superset(h.get_optype(*n).tag()) && !reachable.contains(n)) .collect::>(); for n in unreachable { h.remove_subtree(n); From eaca2e7a44fac56fd343195b45033527c43630ec Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Fri, 20 Dec 2024 17:08:58 +0000 Subject: [PATCH 18/22] Use petgraph::visit::Walker rather than std::iter::from_fn --- hugr-passes/src/dead_func_removal.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/hugr-passes/src/dead_func_removal.rs b/hugr-passes/src/dead_func_removal.rs index 7c0f7821f..903d94c3a 100644 --- a/hugr-passes/src/dead_func_removal.rs +++ b/hugr-passes/src/dead_func_removal.rs @@ -8,7 +8,7 @@ use hugr_core::{ ops::{OpTag, OpTrait}, HugrView, Node, }; -use petgraph::visit::Dfs; +use petgraph::visit::{Dfs, Walker}; use crate::validation::{ValidatePassError, ValidationLevel}; @@ -32,7 +32,7 @@ fn reachable_funcs<'a>( ) -> Result + 'a, RemoveDeadFuncsError> { let g = cg.graph(); let mut entry_points = entry_points.into_iter(); - let mut searcher = if h.get_optype(h.root()).is_module() { + let searcher = if h.get_optype(h.root()).is_module() { let mut d = Dfs::new(g, 0.into()); d.stack.clear(); for n in entry_points { @@ -49,11 +49,9 @@ fn reachable_funcs<'a>( } Dfs::new(g, cg.node_index(h.root()).unwrap()) }; - Ok(std::iter::from_fn(move || { - searcher.next(g).map(|i| match g.node_weight(i).unwrap() { - CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, - CallGraphNode::NonFuncRoot => h.root(), - }) + Ok(searcher.iter(g).map(|i| match g.node_weight(i).unwrap() { + CallGraphNode::FuncDefn(n) | CallGraphNode::FuncDecl(n) => *n, + CallGraphNode::NonFuncRoot => h.root(), })) } From 393a476e60c56a58a4b55ca8f746f71435ba7270 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Mon, 23 Dec 2024 14:24:28 +0000 Subject: [PATCH 19/22] dead_func_removal -> dead_funcs --- hugr-passes/src/{dead_func_removal.rs => dead_funcs.rs} | 0 hugr-passes/src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename hugr-passes/src/{dead_func_removal.rs => dead_funcs.rs} (100%) diff --git a/hugr-passes/src/dead_func_removal.rs b/hugr-passes/src/dead_funcs.rs similarity index 100% rename from hugr-passes/src/dead_func_removal.rs rename to hugr-passes/src/dead_funcs.rs diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index eb183731e..ffc739933 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -3,8 +3,8 @@ pub mod call_graph; pub mod const_fold; pub mod dataflow; -mod dead_func_removal; -pub use dead_func_removal::{remove_dead_funcs, RemoveDeadFuncsPass}; +mod dead_funcs; +pub use dead_funcs::{remove_dead_funcs, RemoveDeadFuncsPass}; pub mod force_order; mod half_node; pub mod lower; From 53389c761ad9b82cb502378c61e6f5b6937ff6f2 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Mon, 23 Dec 2024 14:29:32 +0000 Subject: [PATCH 20/22] Reinstate monomorphize calling remove_polyfuncs with note re. planned change --- hugr-passes/src/monomorphize.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index 092cabf45..7e1566073 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -14,7 +14,6 @@ use hugr_core::hugr::{hugrmut::HugrMut, Hugr, HugrView, OpType}; use itertools::Itertools as _; use thiserror::Error; -use crate::remove_dead_funcs; /// Replaces calls to polymorphic functions with calls to new monomorphic /// instantiations of the polymorphic ones. @@ -22,7 +21,8 @@ use crate::remove_dead_funcs; /// If the Hugr is [Module](OpType::Module)-rooted, /// * then the original polymorphic [FuncDefn]s are left untouched (including Calls inside them) /// - [remove_dead_funcs] can be used when no other Hugr will be linked in that might instantiate these -/// * else, the originals are removed (they are invisible from outside the Hugr). +/// * else, the originals are removed (they are invisible from outside the Hugr); however, note +/// that this behaviour is expected to change in a future release to match Module-rooted Hugrs. /// /// If the Hugr is [FuncDefn](OpType::FuncDefn)-rooted with polymorphic /// signature then the HUGR will not be modified. @@ -47,7 +47,8 @@ fn monomorphize_ref(h: &mut impl HugrMut) { if !is_polymorphic_funcdefn(h.get_optype(root)) { mono_scan(h, root, None, &mut HashMap::new()); if !h.get_optype(root).is_module() { - remove_dead_funcs(h, []).unwrap(); // no-entry-points always succeeds + #[allow(deprecated)] // TODO remove in next breaking release + remove_polyfuncs_ref(h); } } } From 6b496f192123a54121b68c1ae7e1eb2c58310cb2 Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Mon, 23 Dec 2024 14:32:07 +0000 Subject: [PATCH 21/22] fmt --- hugr-passes/src/monomorphize.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index 7e1566073..3f2349bd5 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -14,7 +14,6 @@ use hugr_core::hugr::{hugrmut::HugrMut, Hugr, HugrView, OpType}; use itertools::Itertools as _; use thiserror::Error; - /// Replaces calls to polymorphic functions with calls to new monomorphic /// instantiations of the polymorphic ones. /// From 4a07dee5f7ff94c4276ef372a3c809b9fdc8070a Mon Sep 17 00:00:00 2001 From: Alan Lawrence Date: Mon, 23 Dec 2024 14:37:15 +0000 Subject: [PATCH 22/22] Also deprecate remove_polyfuncs_ref; fix docs --- hugr-passes/src/monomorphize.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index 3f2349bd5..95d26c557 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -19,7 +19,7 @@ use thiserror::Error; /// /// If the Hugr is [Module](OpType::Module)-rooted, /// * then the original polymorphic [FuncDefn]s are left untouched (including Calls inside them) -/// - [remove_dead_funcs] can be used when no other Hugr will be linked in that might instantiate these +/// - [crate::remove_dead_funcs] can be used when no other Hugr will be linked in that might instantiate these /// * else, the originals are removed (they are invisible from outside the Hugr); however, note /// that this behaviour is expected to change in a future release to match Module-rooted Hugrs. /// @@ -46,7 +46,7 @@ fn monomorphize_ref(h: &mut impl HugrMut) { if !is_polymorphic_funcdefn(h.get_optype(root)) { mono_scan(h, root, None, &mut HashMap::new()); if !h.get_optype(root).is_module() { - #[allow(deprecated)] // TODO remove in next breaking release + #[allow(deprecated)] // TODO remove in next breaking release and update docs remove_polyfuncs_ref(h); } } @@ -56,16 +56,21 @@ fn monomorphize_ref(h: &mut impl HugrMut) { /// calls from *monomorphic* code, this will make the Hugr invalid (call [monomorphize] /// first). /// -/// Deprecated: use [remove_dead_funcs] instead. +/// Deprecated: use [crate::remove_dead_funcs] instead. #[deprecated( since = "0.14.1", - note = "Use hugr::algorithms::call_graph::RemoveDeadFuncsPass instead" + note = "Use hugr::algorithms::dead_funcs::RemoveDeadFuncsPass instead" )] pub fn remove_polyfuncs(mut h: Hugr) -> Hugr { + #[allow(deprecated)] // we are in a deprecated function, so remove both at same time remove_polyfuncs_ref(&mut h); h } +#[deprecated( + since = "0.14.1", + note = "Use hugr::algorithms::dead_funcs::RemoveDeadFuncsPass instead" +)] fn remove_polyfuncs_ref(h: &mut impl HugrMut) { let mut pfs_to_delete = Vec::new(); let mut to_scan = Vec::from_iter(h.children(h.root()));