1use std::collections::BTreeSet;
4
5use itertools::Itertools;
6use slotmap::{SecondaryMap, SparseSecondaryMap};
7
8use super::meta_graph::DfirGraph;
9use super::ops::DelayType;
10use super::{
11 Color, GraphEdgeId, GraphLoopId, GraphNode, GraphNodeId, GraphSubgraphId, HandoffKind,
12};
13use crate::diagnostic::{Diagnostic, Level};
14use crate::graph::graph_algorithms::SubgraphMerge;
15
16fn find_edge_barriers(
22 partitioned_graph: &DfirGraph,
23) -> (
24 SecondaryMap<GraphEdgeId, DelayType>,
25 Vec<(GraphNodeId, GraphNodeId)>,
26) {
27 let mut tick_edges = SecondaryMap::new();
28 let mut barrier_pairs = Vec::new();
29
30 for (edge_id, (src, dst)) in partitioned_graph.edges() {
31 let Some(op_inst) = partitioned_graph.node_op_inst(dst) else {
32 continue;
33 };
34 let (_src_port, dst_port) = partitioned_graph.edge_ports(edge_id);
35 let Some(delay_type) = (op_inst.op_constraints.input_delaytype_fn)(dst_port) else {
36 continue;
37 };
38
39 barrier_pairs.push((src, dst));
40 tick_edges.insert(edge_id, delay_type);
41 }
42
43 (tick_edges, barrier_pairs)
44}
45
46fn find_access_group_ordering(partitioned_graph: &DfirGraph) -> Vec<(GraphNodeId, GraphNodeId)> {
49 let mut pairs = Vec::new();
50 let refs_by_target = partitioned_graph.node_handoff_reference_groups();
51 for (_handoff, groups) in refs_by_target {
52 for (group_a, group_b) in groups.values().tuple_windows() {
53 for &(node_a, _, _) in group_a {
54 for &(node_b, _, _) in group_b {
55 assert_ne!(
57 node_a, node_b,
58 "encounted conflicted or cyclical handoff references\n{:?}\n{:?}",
59 group_a, group_b,
60 );
61 pairs.push((node_a, node_b));
62 }
63 }
64 }
65 }
66 pairs
67}
68
69fn find_subgraph_unionfind(
70 partitioned_graph: &DfirGraph,
71 tick_edges: &SecondaryMap<GraphEdgeId, DelayType>,
72 edge_barrier_pairs: &[(GraphNodeId, GraphNodeId)],
73 access_group_pairs: &[(GraphNodeId, GraphNodeId)],
74) -> Result<(SubgraphMerge<GraphNodeId>, BTreeSet<GraphEdgeId>), Diagnostic> {
75 let mut node_color = partitioned_graph
80 .node_ids()
81 .filter_map(|node_id| {
82 let op_color = partitioned_graph.node_color(node_id)?;
83 Some((node_id, op_color))
84 })
85 .collect::<SparseSecondaryMap<_, _>>();
86
87 let mut all_preds: SecondaryMap<GraphNodeId, Vec<GraphNodeId>> = SecondaryMap::new();
89
90 for (edge_id, (src, dst)) in partitioned_graph.edges() {
92 if !tick_edges.contains_key(edge_id) {
93 all_preds.entry(dst).unwrap().or_default().push(src);
94 }
95 }
96
97 for node_id in partitioned_graph.node_ids() {
99 for handoff_ref in partitioned_graph.node_handoff_references(node_id).iter() {
100 if let Some(src) = handoff_ref.node_id {
101 all_preds.entry(node_id).unwrap().or_default().push(src);
102 if let GraphNode::Handoff { .. } = partitioned_graph.node(src) {
105 for (_edge, consumer) in partitioned_graph.node_successors(src) {
106 all_preds
107 .entry(consumer)
108 .unwrap()
109 .or_default()
110 .push(node_id);
111 }
112 }
113 }
114 }
115 }
116
117 for &(src, dst) in access_group_pairs {
119 all_preds.entry(dst).unwrap().or_default().push(src);
120 }
121
122 let enemies = edge_barrier_pairs
124 .iter()
125 .copied()
126 .chain(access_group_pairs.iter().copied())
127 .chain(partitioned_graph.node_ids().flat_map(|dst| {
128 partitioned_graph
129 .node_handoff_references(dst)
130 .iter()
131 .filter_map(|r| r.node_id)
132 .map(move |src| (src, dst))
133 }));
134
135 let mut subgraph_unionfind = SubgraphMerge::<GraphNodeId>::new(
136 partitioned_graph.node_ids(),
137 |node_id| all_preds.get(node_id).into_iter().flatten().copied(),
138 enemies,
139 )
140 .map_err(|cycle| {
141 let span = cycle
142 .first()
143 .map(|&node_id| partitioned_graph.node(node_id).span())
144 .unwrap_or_else(proc_macro2::Span::call_site);
145 let node_cycle = cycle
146 .iter()
147 .map(|&node_id| partitioned_graph.node(node_id).to_pretty_string())
148 .collect::<Vec<_>>();
149 Diagnostic::spanned(
150 span,
151 Level::Error,
152 format!(
153 "Cyclical dataflow within a tick is not supported. Use `defer_tick()` or `defer_tick_lazy()` to break the cycle across ticks. \
154 Cycle: {:?}",
155 node_cycle,
156 ),
157 )
158 })?;
159
160 let mut handoff_edges: BTreeSet<GraphEdgeId> = partitioned_graph.edge_ids().collect();
163 let mut progress = true;
172 while progress {
173 progress = false;
174 for (edge_id, (src, dst)) in partitioned_graph.edges().collect::<Vec<_>>() {
176 if matches!(partitioned_graph.node(src), GraphNode::Handoff { .. })
178 || matches!(partitioned_graph.node(dst), GraphNode::Handoff { .. })
179 {
180 handoff_edges.remove(&edge_id);
181 continue;
182 }
183
184 if subgraph_unionfind.same_set(src, dst) {
186 continue;
189 }
190
191 if partitioned_graph.node_loop(src) != partitioned_graph.node_loop(dst) {
193 continue;
194 }
195
196 if can_connect_colorize(&mut node_color, src, dst) {
197 let ok = subgraph_unionfind.try_merge(src, dst);
200 if ok {
201 assert!(handoff_edges.remove(&edge_id));
202 progress = true;
203 }
204 }
205 }
206 }
207
208 Ok((subgraph_unionfind, handoff_edges))
209}
210
211fn make_subgraphs(
213 partitioned_graph: &mut DfirGraph,
214 tick_edges: &mut SecondaryMap<GraphEdgeId, DelayType>,
215 edge_barrier_pairs: &[(GraphNodeId, GraphNodeId)],
216 access_group_pairs: &[(GraphNodeId, GraphNodeId)],
217) -> Result<(), Diagnostic> {
218 let (subgraph_merge, handoff_edges) = find_subgraph_unionfind(
227 partitioned_graph,
228 tick_edges,
229 edge_barrier_pairs,
230 access_group_pairs,
231 )?;
232
233 for edge_id in handoff_edges {
235 let (src_id, dst_id) = partitioned_graph.edge(edge_id);
236
237 let src_node = partitioned_graph.node(src_id);
239 let dst_node = partitioned_graph.node(dst_id);
240 if matches!(src_node, GraphNode::Handoff { .. })
241 || matches!(dst_node, GraphNode::Handoff { .. })
242 {
243 continue;
244 }
245
246 let hoff = GraphNode::Handoff {
247 kind: HandoffKind::Vec,
248 src_span: src_node.span(),
249 dst_span: dst_node.span(),
250 };
251 let (_node_id, out_edge_id) = partitioned_graph.insert_intermediate_node(edge_id, hoff);
252
253 if let Some(delay_type) = tick_edges.remove(edge_id) {
255 tick_edges.insert(out_edge_id, delay_type);
256 }
257 }
258
259 let mut flat_toposort = Vec::new();
263 for nodes in subgraph_merge.subgraphs() {
264 if nodes.is_empty() {
265 continue;
266 }
267 if nodes
269 .iter()
270 .any(|&n| matches!(partitioned_graph.node(n), GraphNode::Handoff { .. }))
271 {
272 continue;
273 }
274 let sg_id = partitioned_graph.insert_subgraph(nodes.to_vec()).unwrap();
275 flat_toposort.push(sg_id);
276 }
277
278 let subgraph_toposort = make_loops_contiguous(partitioned_graph, &flat_toposort);
280 partitioned_graph.set_subgraph_toposort(subgraph_toposort);
281
282 Ok(())
283}
284
285fn make_loops_contiguous(
298 graph: &DfirGraph,
299 flat_order: &[GraphSubgraphId],
300) -> Vec<GraphSubgraphId> {
301 use std::collections::HashMap;
302
303 let mut loop_descendants = HashMap::<GraphLoopId, Vec<GraphSubgraphId>>::new();
306 for &sg_id in flat_order {
307 let mut current = graph.subgraph_loop(sg_id);
308 while let Some(loop_id) = current {
309 loop_descendants.entry(loop_id).or_default().push(sg_id);
310 current = graph.loop_parent(loop_id);
311 }
312 }
313
314 fn helper(
315 graph: &DfirGraph,
316 flat_order: &[GraphSubgraphId],
317 current_loop: Option<GraphLoopId>,
318 loop_descendants: &mut HashMap<GraphLoopId, Vec<GraphSubgraphId>>,
319 output: &mut Vec<GraphSubgraphId>,
320 ) {
321 for &sg_id in flat_order {
322 let sg_loop = graph.subgraph_loop(sg_id);
323 if current_loop == sg_loop {
324 output.push(sg_id);
326 continue;
327 }
328 let sg_loop = sg_loop.expect("root-level subgraph cannot be within a loop: `sg_loop == None` implies `current_loop == None`");
329 if current_loop == graph.loop_parent(sg_loop) {
330 if let Some(inner_order) = loop_descendants.remove(&sg_loop) {
333 helper(graph, &inner_order, Some(sg_loop), loop_descendants, output);
334 }
335 }
336 }
338 }
339
340 let mut output = Vec::with_capacity(flat_order.len());
341 helper(graph, flat_order, None, &mut loop_descendants, &mut output);
342 output
343}
344
345fn can_connect_colorize(
351 node_color: &mut SparseSecondaryMap<GraphNodeId, Color>,
352 src: GraphNodeId,
353 dst: GraphNodeId,
354) -> bool {
355 let can_connect = match (node_color.get(src), node_color.get(dst)) {
360 (None, None) => false,
363
364 (None, Some(Color::Pull | Color::Comp)) => {
366 node_color.insert(src, Color::Pull);
367 true
368 }
369 (None, Some(Color::Push | Color::Hoff)) => {
370 node_color.insert(src, Color::Push);
371 true
372 }
373
374 (Some(Color::Pull | Color::Hoff), None) => {
376 node_color.insert(dst, Color::Pull);
377 true
378 }
379 (Some(Color::Comp | Color::Push), None) => {
380 node_color.insert(dst, Color::Push);
381 true
382 }
383
384 (Some(Color::Pull), Some(Color::Pull)) => true,
386 (Some(Color::Pull), Some(Color::Comp)) => true,
387 (Some(Color::Pull), Some(Color::Push)) => true,
388
389 (Some(Color::Comp), Some(Color::Pull)) => false,
390 (Some(Color::Comp), Some(Color::Comp)) => false,
391 (Some(Color::Comp), Some(Color::Push)) => true,
392
393 (Some(Color::Push), Some(Color::Pull)) => false,
394 (Some(Color::Push), Some(Color::Comp)) => false,
395 (Some(Color::Push), Some(Color::Push)) => true,
396
397 (Some(Color::Hoff), Some(_)) => false,
399 (Some(_), Some(Color::Hoff)) => false,
400 };
401 can_connect
402}
403
404fn mark_tick_boundary_handoffs(
411 partitioned_graph: &mut DfirGraph,
412 tick_edges: &SecondaryMap<GraphEdgeId, DelayType>,
413) {
414 let tick_handoffs: Vec<_> = partitioned_graph
415 .nodes()
416 .filter_map(|(hoff_id, hoff)| {
417 if !matches!(hoff, GraphNode::Handoff { .. }) {
418 return None;
419 }
420 if partitioned_graph.node_degree_out(hoff_id) == 0 {
421 return None;
422 }
423 let (succ_edge, _) = partitioned_graph.node_successors(hoff_id).next().unwrap();
424 let &delay_type = tick_edges.get(succ_edge)?;
425
426 let consumer_loop = partitioned_graph
428 .node_successors(hoff_id)
429 .next()
430 .and_then(|(_, succ)| partitioned_graph.node_loop(succ));
431 let effective_delay_type = if let Some(loop_id) = consumer_loop {
432 if partitioned_graph.loop_parent(loop_id).is_some() {
433 match delay_type {
435 DelayType::Tick => DelayType::Loop,
436 DelayType::TickLazy => DelayType::LoopLazy,
437 other => other,
438 }
439 } else {
440 delay_type
441 }
442 } else {
443 delay_type
444 };
445
446 Some((hoff_id, effective_delay_type))
447 })
448 .collect();
449
450 for (hoff_id, delay_type) in tick_handoffs {
451 partitioned_graph.set_handoff_delay_type(hoff_id, delay_type);
452 }
453}
454
455pub fn partition_graph(flat_graph: DfirGraph) -> Result<DfirGraph, Diagnostic> {
459 let (mut tick_edges, edge_barrier_pairs) = find_edge_barriers(&flat_graph);
460 let access_group_pairs = find_access_group_ordering(&flat_graph);
461 let mut partitioned_graph = flat_graph;
462
463 make_subgraphs(
465 &mut partitioned_graph,
466 &mut tick_edges,
467 &edge_barrier_pairs,
468 &access_group_pairs,
469 )?;
470
471 mark_tick_boundary_handoffs(&mut partitioned_graph, &tick_edges);
473
474 Ok(partitioned_graph)
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480 use crate::graph::GraphNode;
481
482 fn make_op_node(graph: &mut DfirGraph, loop_ctx: Option<GraphLoopId>) -> GraphNodeId {
483 let operator: crate::parse::Operator = syn::parse_quote! { identity() };
484 graph.insert_node(GraphNode::Operator(operator), None, loop_ctx)
485 }
486
487 #[test]
490 fn test_make_loops_contiguous_basic() {
491 let mut graph = DfirGraph::default();
492
493 let loop_id = graph.insert_loop(None);
495
496 let op_a = make_op_node(&mut graph, None);
498 let op_b = make_op_node(&mut graph, None);
499 let op_c = make_op_node(&mut graph, Some(loop_id));
500 let op_d = make_op_node(&mut graph, Some(loop_id));
501
502 let sg_a = graph.insert_subgraph(vec![op_a]).unwrap();
504 let sg_b = graph.insert_subgraph(vec![op_b]).unwrap();
505 let sg_c = graph.insert_subgraph(vec![op_c]).unwrap();
506 let sg_d = graph.insert_subgraph(vec![op_d]).unwrap();
507
508 let flat_order = vec![sg_a, sg_c, sg_b, sg_d];
510 let result = make_loops_contiguous(&graph, &flat_order);
511
512 assert_eq!(result, vec![sg_a, sg_c, sg_d, sg_b]);
514 }
515
516 #[test]
518 fn test_make_loops_contiguous_independent_loops() {
519 let mut graph = DfirGraph::default();
520
521 let loop1 = graph.insert_loop(None);
522 let loop2 = graph.insert_loop(None);
523
524 let op_c = make_op_node(&mut graph, Some(loop1));
526 let op_d = make_op_node(&mut graph, Some(loop1));
527 let op_e = make_op_node(&mut graph, Some(loop2));
528 let op_f = make_op_node(&mut graph, Some(loop2));
529
530 let sg_c = graph.insert_subgraph(vec![op_c]).unwrap();
531 let sg_d = graph.insert_subgraph(vec![op_d]).unwrap();
532 let sg_e = graph.insert_subgraph(vec![op_e]).unwrap();
533 let sg_f = graph.insert_subgraph(vec![op_f]).unwrap();
534
535 let flat_order = vec![sg_c, sg_e, sg_d, sg_f];
537 let result = make_loops_contiguous(&graph, &flat_order);
538
539 let pos_c = result.iter().position(|&s| s == sg_c).unwrap();
541 let pos_d = result.iter().position(|&s| s == sg_d).unwrap();
542 let pos_e = result.iter().position(|&s| s == sg_e).unwrap();
543 let pos_f = result.iter().position(|&s| s == sg_f).unwrap();
544
545 assert!(pos_c < pos_d);
547 assert!(pos_e < pos_f);
548
549 assert_eq!(pos_d - pos_c, 1, "Loop1 subgraphs must be contiguous");
551 assert_eq!(pos_f - pos_e, 1, "Loop2 subgraphs must be contiguous");
552 }
553
554 #[test]
556 fn test_make_loops_contiguous_nested() {
557 let mut graph = DfirGraph::default();
558
559 let outer = graph.insert_loop(None);
560 let inner = graph.insert_loop(Some(outer));
561
562 let op_a = make_op_node(&mut graph, Some(outer));
564 let op_b = make_op_node(&mut graph, Some(inner));
565 let op_c = make_op_node(&mut graph, Some(inner));
566 let op_d = make_op_node(&mut graph, Some(outer));
567
568 let sg_a = graph.insert_subgraph(vec![op_a]).unwrap();
569 let sg_b = graph.insert_subgraph(vec![op_b]).unwrap();
570 let sg_c = graph.insert_subgraph(vec![op_c]).unwrap();
571 let sg_d = graph.insert_subgraph(vec![op_d]).unwrap();
572
573 let flat_order = vec![sg_a, sg_b, sg_c, sg_d];
575 let result = make_loops_contiguous(&graph, &flat_order);
576
577 assert_eq!(result, vec![sg_a, sg_b, sg_c, sg_d]);
579 }
580
581 #[test]
583 fn test_make_loops_contiguous_nested_interleaved() {
584 let mut graph = DfirGraph::default();
585
586 let outer = graph.insert_loop(None);
587 let inner = graph.insert_loop(Some(outer));
588
589 let op_a = make_op_node(&mut graph, Some(outer));
590 let op_b = make_op_node(&mut graph, Some(inner));
591 let op_c = make_op_node(&mut graph, Some(inner));
592 let op_d = make_op_node(&mut graph, Some(outer));
593
594 let sg_a = graph.insert_subgraph(vec![op_a]).unwrap();
595 let sg_b = graph.insert_subgraph(vec![op_b]).unwrap();
596 let sg_c = graph.insert_subgraph(vec![op_c]).unwrap();
597 let sg_d = graph.insert_subgraph(vec![op_d]).unwrap();
598
599 let flat_order = vec![sg_a, sg_b, sg_d, sg_c];
601 let result = make_loops_contiguous(&graph, &flat_order);
602
603 assert_eq!(result, vec![sg_a, sg_b, sg_c, sg_d]);
605 }
606}