1use std::collections::{HashSet, VecDeque};
3
4use itertools::Itertools;
5use petgraph::algo::tarjan_scc;
6
7use hugr_core::hugr::{hugrmut::HugrMut, patch::inline_call::InlineCall};
8use hugr_core::module_graph::{ModuleGraph, StaticNode};
9
10#[derive(Clone, Debug, thiserror::Error, PartialEq)]
12#[non_exhaustive]
13#[deprecated(
14 note = "`hugr-passes` is deprecated. Use tket::passes instead",
15 since = "0.26.2"
16)]
17pub enum InlineFuncsError {}
18
19#[deprecated(
29 note = "`hugr-passes` is deprecated. Use tket::passes instead",
30 since = "0.26.2"
31)]
32pub fn inline_acyclic<H: HugrMut>(
33 h: &mut H,
34 call_predicate: impl Fn(&H, H::Node) -> bool,
35) -> Result<(), InlineFuncsError> {
36 let cg = ModuleGraph::new(&*h);
37 let g = cg.graph();
38 let all_funcs_in_cycles = tarjan_scc(g)
39 .into_iter()
40 .flat_map(|mut ns| {
41 if let Ok(n) = ns.iter().exactly_one()
42 && g.edges_connecting(*n, *n).next().is_none()
43 {
44 ns.clear(); }
46 ns.into_iter().map(|n| {
47 let StaticNode::FuncDefn(fd) = g.node_weight(n).unwrap() else {
48 panic!("Expected only FuncDefns in sccs")
49 };
50 *fd
51 })
52 })
53 .collect::<HashSet<_>>();
54 let target_funcs: HashSet<H::Node> = h
55 .children(h.module_root())
56 .filter(|n| h.get_optype(*n).is_func_defn() && !all_funcs_in_cycles.contains(n))
57 .collect();
58 let mut q = VecDeque::from([h.entrypoint()]);
59 while let Some(n) = q.pop_front() {
60 if h.get_optype(n).is_call()
61 && let Some(t) = h.static_source(n)
62 && target_funcs.contains(&t)
63 && call_predicate(h, n)
64 {
65 h.apply_patch(InlineCall::new(n)).unwrap();
67 }
68 q.extend(h.children(n));
70 }
71 Ok(())
72}
73
74#[cfg(test)]
75mod test {
76 use std::collections::HashSet;
77
78 use itertools::Itertools;
79 use rstest::rstest;
80
81 use hugr_core::HugrView;
82 use hugr_core::builder::{Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder};
83 use hugr_core::core::HugrNode;
84 use hugr_core::module_graph::{ModuleGraph, StaticNode};
85 use hugr_core::ops::OpType;
86 use hugr_core::{Hugr, extension::prelude::qb_t, types::Signature};
87
88 use super::inline_acyclic;
89
90 fn make_test_hugr() -> Hugr {
96 let sig = || Signature::new_endo([qb_t()]);
97 let mut mb = ModuleBuilder::new();
98 let x = mb.declare("x", sig().into()).unwrap();
99 let a = {
100 let mut fb = mb.define_function("a", sig()).unwrap();
101 let ins = fb.input_wires();
102 let res = fb.call(&x, &[], ins).unwrap();
103 fb.finish_with_outputs(res.outputs()).unwrap()
104 };
105 let c = {
106 let fb = mb.define_function("c", sig()).unwrap();
107 let ins = fb.input_wires();
108 fb.finish_with_outputs(ins).unwrap()
109 };
110 let b = {
111 let mut fb = mb.define_function("b", sig()).unwrap();
112 let ins = fb.input_wires();
113 let res = fb.call(c.handle(), &[], ins).unwrap().outputs();
114 fb.finish_with_outputs(res).unwrap()
115 };
116 let f = mb.declare("f", sig().into()).unwrap();
117 let g = {
118 let mut fb = mb.define_function("g", sig()).unwrap();
119 let ins = fb.input_wires();
120 let c1 = fb.call(&f, &[], ins).unwrap();
121 let c2 = fb.call(b.handle(), &[], c1.outputs()).unwrap();
122 fb.finish_with_outputs(c2.outputs()).unwrap()
123 };
124 let _f = {
125 let mut fb = mb.define_declaration(&f).unwrap();
126 let ins = fb.input_wires();
127 let c1 = fb.call(g.handle(), &[], ins).unwrap();
128 let c2 = fb.call(a.handle(), &[], c1.outputs()).unwrap();
129 fb.finish_with_outputs(c2.outputs()).unwrap()
130 };
131 mb.finish_hugr().unwrap()
132 }
133
134 fn find_func<H: HugrView>(h: &H, name: &str) -> H::Node {
135 h.children(h.module_root())
136 .find(|n| {
137 h.get_optype(*n)
138 .as_func_defn()
139 .is_some_and(|fd| fd.func_name() == name)
140 })
141 .unwrap()
142 }
143
144 #[rstest]
145 #[case(["a", "b", "c"], ["a", "b", "c"], [vec!["g", "x"], vec!["f"], vec!["x"], vec![], vec![]])]
146 #[case(["a", "b"], ["a", "b"], [vec!["g", "x"], vec!["f", "c"], vec!["x"], vec!["c"], vec![]])]
147 #[case(["c"], ["c"], [vec!["g", "a"], vec!("f", "b"), vec!["x"], vec![], vec![]])]
148 fn test_inline(
149 #[case] req: impl IntoIterator<Item = &'static str>,
150 #[case] check_not_called: impl IntoIterator<Item = &'static str>,
151 #[case] calls_fgabc: [Vec<&'static str>; 5],
152 ) {
153 let mut h = make_test_hugr();
154 let target_funcs = req
155 .into_iter()
156 .map(|name| find_func(&h, name))
157 .collect::<HashSet<_>>();
158 inline_acyclic(&mut h, |h, call| {
159 let tgt = h.static_source(call).unwrap();
160 assert!(["a", "b", "c"].contains(&func_name(h, tgt).as_str()));
162 target_funcs.contains(&tgt)
163 })
164 .unwrap();
165 let cg = ModuleGraph::new(&h);
166 for fname in check_not_called {
167 let fnode = find_func(&h, fname);
168 let fnode = cg.node_index(fnode).unwrap();
169 assert_eq!(
170 None,
171 cg.graph()
172 .edges_directed(fnode, petgraph::Direction::Incoming)
173 .next()
174 );
175 }
176 for (fname, tgts) in ["f", "g", "a", "b", "c"].into_iter().zip_eq(calls_fgabc) {
177 let fnode = find_func(&h, fname);
178 assert_eq!(
179 outgoing_calls(&cg, fnode)
180 .into_iter()
181 .map(|n| func_name(&h, n).as_str())
182 .collect::<HashSet<_>>(),
183 HashSet::from_iter(tgts),
184 "Calls from {fname}"
185 );
186 }
187 }
188
189 fn outgoing_calls<N: HugrNode>(cg: &ModuleGraph<N>, src: N) -> Vec<N> {
190 cg.out_edges(src).map(|(_, tgt)| func_node(tgt)).collect()
191 }
192
193 #[test]
194 fn test_filter_caller() {
195 let mut h = make_test_hugr();
196 let [g, b, c] = ["g", "b", "c"].map(|n| find_func(&h, n));
197 inline_acyclic(&mut h, |h, mut call| {
199 loop {
200 if call == g {
201 return true;
202 };
203 let Some(parent) = h.get_parent(call) else {
204 return false;
205 };
206 call = parent;
207 }
208 })
209 .unwrap();
210 let cg = ModuleGraph::new(&h);
211 assert_eq!(outgoing_calls(&cg, g), [find_func(&h, "f")]);
213 assert_eq!(outgoing_calls(&cg, b), [c]);
215 }
216
217 fn func_node<N: Copy>(cgn: &StaticNode<N>) -> N {
218 match cgn {
219 StaticNode::FuncDecl(n) | StaticNode::FuncDefn(n) => *n,
220 _ => panic!(),
221 }
222 }
223
224 fn func_name<H: HugrView>(h: &H, n: H::Node) -> &String {
225 match h.get_optype(n) {
226 OpType::FuncDecl(fd) => fd.func_name(),
227 OpType::FuncDefn(fd) => fd.func_name(),
228 _ => panic!(),
229 }
230 }
231}