geographdb_core/algorithms/
natural_loops.rs1use crate::algorithms::astar::CfgGraphNode;
18use crate::algorithms::dominance::{compute_dominance, dominates};
19use std::collections::{HashMap, HashSet, VecDeque};
20
21#[derive(Debug, Clone)]
23pub struct NaturalLoop {
24 pub header: u64,
26 pub latch: u64,
28 pub body: HashSet<u64>,
30 pub pre_header: HashSet<u64>,
32}
33
34#[derive(Debug, Clone)]
36pub struct NaturalLoopResult {
37 pub loops: Vec<NaturalLoop>,
39 pub back_edges: Vec<(u64, u64)>,
41}
42
43pub fn find_back_edges(nodes: &[CfgGraphNode], entry_id: u64) -> Vec<(u64, u64)> {
47 let dom_result = compute_dominance(nodes, entry_id);
48 let mut back_edges = Vec::new();
49
50 for node in nodes {
51 for &succ in &node.successors {
52 if dominates(succ, node.id, &dom_result) {
53 back_edges.push((node.id, succ));
54 }
55 }
56 }
57
58 back_edges
59}
60
61pub fn find_natural_loops(nodes: &[CfgGraphNode], entry_id: u64) -> NaturalLoopResult {
70 let back_edges = find_back_edges(nodes, entry_id);
71 let mut loops = Vec::new();
72
73 let mut predecessors: HashMap<u64, Vec<u64>> = HashMap::new();
75 for node in nodes {
76 for &succ in &node.successors {
77 predecessors.entry(succ).or_default().push(node.id);
78 }
79 }
80
81 for (source, target) in &back_edges {
83 let loop_body = find_loop_body(nodes, *source, *target, &predecessors);
84
85 let mut pre_header = HashSet::new();
87 for &node_id in &loop_body {
88 if let Some(preds) = predecessors.get(&node_id) {
89 for &pred in preds {
90 if !loop_body.contains(&pred) {
91 pre_header.insert(pred);
92 }
93 }
94 }
95 }
96
97 loops.push(NaturalLoop {
98 header: *target,
99 latch: *source,
100 body: loop_body,
101 pre_header,
102 });
103 }
104
105 NaturalLoopResult { loops, back_edges }
106}
107
108fn find_loop_body(
110 _nodes: &[CfgGraphNode],
111 latch: u64,
112 header: u64,
113 predecessors: &HashMap<u64, Vec<u64>>,
114) -> HashSet<u64> {
115 let mut body = HashSet::new();
116 body.insert(latch);
117 body.insert(header);
118
119 let mut worklist: VecDeque<u64> = VecDeque::new();
122 worklist.push_back(latch);
123
124 while let Some(node_id) = worklist.pop_front() {
125 if let Some(preds) = predecessors.get(&node_id) {
126 for &pred in preds {
127 if !body.contains(&pred) {
128 body.insert(pred);
129 worklist.push_back(pred);
130 }
131 }
132 }
133 }
134
135 body
136}
137
138pub fn is_loop_header(node_id: u64, back_edges: &[(u64, u64)]) -> bool {
140 back_edges.iter().any(|&(_, target)| target == node_id)
141}
142
143pub fn get_loop_nesting_depth(loop_header: u64, loops: &[NaturalLoop]) -> usize {
145 let mut depth = 0;
146 for loop_ in loops {
147 if loop_.header != loop_header && loop_.body.contains(&loop_header) {
148 depth += 1;
149 }
150 }
151 depth
152}
153
154pub fn find_innermost_loop_for_node(node_id: u64, loops: &[NaturalLoop]) -> Option<&NaturalLoop> {
156 loops
157 .iter()
158 .filter(|l| l.body.contains(&node_id))
159 .min_by_key(|l| l.body.len())
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_find_back_edges_no_loops() {
168 let nodes = vec![
170 CfgGraphNode {
171 id: 0,
172 x: 0.0,
173 y: 0.0,
174 z: 0.0,
175 successors: vec![1],
176 },
177 CfgGraphNode {
178 id: 1,
179 x: 1.0,
180 y: 0.0,
181 z: 0.0,
182 successors: vec![2],
183 },
184 CfgGraphNode {
185 id: 2,
186 x: 2.0,
187 y: 0.0,
188 z: 0.0,
189 successors: vec![],
190 },
191 ];
192
193 let back_edges = find_back_edges(&nodes, 0);
194 assert_eq!(back_edges.len(), 0);
195 }
196
197 #[test]
198 fn test_find_back_edges_simple_loop() {
199 let nodes = vec![
201 CfgGraphNode {
202 id: 0,
203 x: 0.0,
204 y: 0.0,
205 z: 0.0,
206 successors: vec![1],
207 },
208 CfgGraphNode {
209 id: 1,
210 x: 1.0,
211 y: 0.0,
212 z: 0.0,
213 successors: vec![2],
214 },
215 CfgGraphNode {
216 id: 2,
217 x: 2.0,
218 y: 0.0,
219 z: 0.0,
220 successors: vec![1],
221 },
222 ];
223
224 let back_edges = find_back_edges(&nodes, 0);
225 assert_eq!(back_edges.len(), 1);
226 assert_eq!(back_edges[0], (2, 1));
227 }
228
229 #[test]
230 fn test_find_natural_loops_simple() {
231 let nodes = vec![
233 CfgGraphNode {
234 id: 0,
235 x: 0.0,
236 y: 0.0,
237 z: 0.0,
238 successors: vec![1],
239 },
240 CfgGraphNode {
241 id: 1,
242 x: 1.0,
243 y: 0.0,
244 z: 0.0,
245 successors: vec![2],
246 },
247 CfgGraphNode {
248 id: 2,
249 x: 2.0,
250 y: 0.0,
251 z: 0.0,
252 successors: vec![1],
253 },
254 ];
255
256 let result = find_natural_loops(&nodes, 0);
257 assert_eq!(result.loops.len(), 1);
258 assert_eq!(result.back_edges.len(), 1);
259
260 let loop_ = &result.loops[0];
261 assert_eq!(loop_.header, 1);
262 assert_eq!(loop_.latch, 2);
263 assert!(loop_.body.contains(&1));
264 assert!(loop_.body.contains(&2));
265 }
266
267 #[test]
268 fn test_find_natural_loops_nested() {
269 let nodes = vec![
271 CfgGraphNode {
272 id: 0,
273 x: 0.0,
274 y: 0.0,
275 z: 0.0,
276 successors: vec![1],
277 },
278 CfgGraphNode {
279 id: 1,
280 x: 1.0,
281 y: 0.0,
282 z: 0.0,
283 successors: vec![2, 3],
284 },
285 CfgGraphNode {
286 id: 2,
287 x: 2.0,
288 y: 0.0,
289 z: 0.0,
290 successors: vec![1],
291 },
292 CfgGraphNode {
293 id: 3,
294 x: 3.0,
295 y: 0.0,
296 z: 0.0,
297 successors: vec![4],
298 },
299 CfgGraphNode {
300 id: 4,
301 x: 4.0,
302 y: 0.0,
303 z: 0.0,
304 successors: vec![3],
305 },
306 ];
307
308 let result = find_natural_loops(&nodes, 0);
309 assert!(result.loops.len() >= 2);
310 }
311
312 #[test]
313 fn test_is_loop_header() {
314 let back_edges = vec![(2, 1), (4, 3)];
315 assert!(is_loop_header(1, &back_edges));
316 assert!(is_loop_header(3, &back_edges));
317 assert!(!is_loop_header(0, &back_edges));
318 }
319
320 #[test]
321 fn test_find_innermost_loop_for_node() {
322 let loops = vec![
323 NaturalLoop {
324 header: 1,
325 latch: 2,
326 body: vec![1, 2, 3].into_iter().collect(),
327 pre_header: vec![0].into_iter().collect(),
328 },
329 NaturalLoop {
330 header: 2,
331 latch: 3,
332 body: vec![2, 3].into_iter().collect(),
333 pre_header: vec![1].into_iter().collect(),
334 },
335 ];
336
337 let innermost = find_innermost_loop_for_node(3, &loops);
339 assert!(innermost.is_some());
340 assert_eq!(innermost.unwrap().header, 2);
341 }
342}