1#![warn(missing_docs)]
2
3extern crate proc_macro;
4
5use std::collections::{BTreeMap, BTreeSet};
6use std::fmt::Debug;
7use std::iter::FusedIterator;
8
9use itertools::Itertools;
10use proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree};
11use quote::{ToTokens, format_ident, quote, quote_spanned};
12use serde::{Deserialize, Serialize};
13use slotmap::{Key, SecondaryMap, SlotMap, SparseSecondaryMap};
14use syn::spanned::Spanned;
15
16use super::graph_write::{Dot, GraphWrite, Mermaid};
17use super::ops::{
18 DelayType, FloType, OPERATORS, OperatorWriteOutput, WriteContextArgs, find_op_op_constraints,
19 null_write_iterator_fn,
20};
21use super::{
22 CONTEXT, Color, DiMulGraph, GRAPH, GraphEdgeId, GraphLoopId, GraphNode, GraphNodeId,
23 GraphSubgraphId, HANDOFF_NODE_STR, HandoffKind, MODULE_BOUNDARY_NODE_STR, OperatorInstance,
24 PortIndexValue, SINGLETON_SLOT_NODE_STR, Varname, change_spans, get_operator_generics,
25};
26use crate::diagnostic::{Diagnostic, Diagnostics, Level};
27use crate::pretty_span::{PrettyRowCol, PrettySpan};
28use crate::process_singletons;
29
30#[derive(Clone, Debug, Serialize, Deserialize)]
32pub struct ResolvedHandoffRef {
33 pub node_id: Option<GraphNodeId>,
35 pub is_mut: bool,
37 pub access_group: Option<u32>,
39}
40
41#[derive(Default, Debug, Serialize, Deserialize)]
51pub struct DfirGraph {
52 nodes: SlotMap<GraphNodeId, GraphNode>,
54
55 #[serde(skip)]
58 operator_instances: SecondaryMap<GraphNodeId, OperatorInstance>,
59 operator_tag: SecondaryMap<GraphNodeId, String>,
61 graph: DiMulGraph<GraphNodeId, GraphEdgeId>,
63 ports: SecondaryMap<GraphEdgeId, (PortIndexValue, PortIndexValue)>,
65
66 node_loops: SecondaryMap<GraphNodeId, GraphLoopId>,
68 loop_nodes: SlotMap<GraphLoopId, Vec<GraphNodeId>>,
70 loop_parent: SparseSecondaryMap<GraphLoopId, GraphLoopId>,
72 root_loops: Vec<GraphLoopId>,
74 loop_children: SecondaryMap<GraphLoopId, Vec<GraphLoopId>>,
76
77 node_subgraph: SecondaryMap<GraphNodeId, GraphSubgraphId>,
79
80 subgraph_nodes: SlotMap<GraphSubgraphId, Vec<GraphNodeId>>,
82 subgraph_toposort: Vec<GraphSubgraphId>,
84
85 node_handoff_references: SparseSecondaryMap<GraphNodeId, Vec<ResolvedHandoffRef>>,
87 node_varnames: SparseSecondaryMap<GraphNodeId, Varname>,
89
90 handoff_delay_type: SparseSecondaryMap<GraphNodeId, DelayType>,
94}
95
96impl DfirGraph {
98 pub fn new() -> Self {
100 Default::default()
101 }
102}
103
104impl DfirGraph {
106 pub fn node(&self, node_id: GraphNodeId) -> &GraphNode {
108 self.nodes.get(node_id).expect("Node not found.")
109 }
110
111 pub fn node_op_inst(&self, node_id: GraphNodeId) -> Option<&OperatorInstance> {
116 self.operator_instances.get(node_id)
117 }
118
119 pub fn node_varname(&self, node_id: GraphNodeId) -> Option<&Varname> {
121 self.node_varnames.get(node_id)
122 }
123
124 pub fn node_subgraph(&self, node_id: GraphNodeId) -> Option<GraphSubgraphId> {
126 self.node_subgraph.get(node_id).copied()
127 }
128
129 pub fn node_degree_in(&self, node_id: GraphNodeId) -> usize {
131 self.graph.degree_in(node_id)
132 }
133
134 pub fn node_degree_out(&self, node_id: GraphNodeId) -> usize {
136 self.graph.degree_out(node_id)
137 }
138
139 pub fn node_successors(
141 &self,
142 src: GraphNodeId,
143 ) -> impl '_
144 + DoubleEndedIterator<Item = (GraphEdgeId, GraphNodeId)>
145 + ExactSizeIterator
146 + FusedIterator
147 + Clone
148 + Debug {
149 self.graph.successors(src)
150 }
151
152 pub fn node_predecessors(
154 &self,
155 dst: GraphNodeId,
156 ) -> impl '_
157 + DoubleEndedIterator<Item = (GraphEdgeId, GraphNodeId)>
158 + ExactSizeIterator
159 + FusedIterator
160 + Clone
161 + Debug {
162 self.graph.predecessors(dst)
163 }
164
165 pub fn node_successor_edges(
167 &self,
168 src: GraphNodeId,
169 ) -> impl '_
170 + DoubleEndedIterator<Item = GraphEdgeId>
171 + ExactSizeIterator
172 + FusedIterator
173 + Clone
174 + Debug {
175 self.graph.successor_edges(src)
176 }
177
178 pub fn node_predecessor_edges(
180 &self,
181 dst: GraphNodeId,
182 ) -> impl '_
183 + DoubleEndedIterator<Item = GraphEdgeId>
184 + ExactSizeIterator
185 + FusedIterator
186 + Clone
187 + Debug {
188 self.graph.predecessor_edges(dst)
189 }
190
191 pub fn node_successor_nodes(
193 &self,
194 src: GraphNodeId,
195 ) -> impl '_
196 + DoubleEndedIterator<Item = GraphNodeId>
197 + ExactSizeIterator
198 + FusedIterator
199 + Clone
200 + Debug {
201 self.graph.successor_vertices(src)
202 }
203
204 pub fn node_predecessor_nodes(
206 &self,
207 dst: GraphNodeId,
208 ) -> impl '_
209 + DoubleEndedIterator<Item = GraphNodeId>
210 + ExactSizeIterator
211 + FusedIterator
212 + Clone
213 + Debug {
214 self.graph.predecessor_vertices(dst)
215 }
216
217 pub fn node_ids(&self) -> slotmap::basic::Keys<'_, GraphNodeId, GraphNode> {
219 self.nodes.keys()
220 }
221
222 pub fn nodes(&self) -> slotmap::basic::Iter<'_, GraphNodeId, GraphNode> {
224 self.nodes.iter()
225 }
226
227 pub fn insert_node(
229 &mut self,
230 node: GraphNode,
231 varname_opt: Option<Ident>,
232 loop_opt: Option<GraphLoopId>,
233 ) -> GraphNodeId {
234 let node_id = self.nodes.insert(node);
235 if let Some(varname) = varname_opt {
236 self.node_varnames.insert(node_id, Varname(varname));
237 }
238 if let Some(loop_id) = loop_opt {
239 self.node_loops.insert(node_id, loop_id);
240 self.loop_nodes[loop_id].push(node_id);
241 }
242 node_id
243 }
244
245 pub fn insert_node_op_inst(&mut self, node_id: GraphNodeId, op_inst: OperatorInstance) {
247 assert!(matches!(
248 self.nodes.get(node_id),
249 Some(GraphNode::Operator(_))
250 ));
251 let old_inst = self.operator_instances.insert(node_id, op_inst);
252 assert!(old_inst.is_none());
253 }
254
255 pub fn insert_node_op_insts_all(&mut self, diagnostics: &mut Diagnostics) {
257 let mut op_insts = Vec::new();
262 let mut handoff_nodes: Vec<(GraphNodeId, HandoffKind, Span)> = Vec::new();
264
265 for (node_id, node) in self.nodes() {
266 let GraphNode::Operator(operator) = node else {
267 continue;
268 };
269 if self.node_op_inst(node_id).is_some() {
270 continue;
271 };
272
273 let handoff_kind = match &*operator.name_string() {
275 "handoff" => Some(HandoffKind::Vec),
276 "singleton" => Some(HandoffKind::Singleton),
277 "optional" => Some(HandoffKind::Optional),
278 _ => None,
279 };
280 if let Some(kind) = handoff_kind {
281 if !operator.args.is_empty() {
282 diagnostics.push(Diagnostic::spanned(
283 operator.path.span(),
284 Level::Error,
285 format!("`{}` takes no arguments.", operator.name_string()),
286 ));
287 }
288 if operator.type_arguments().is_some() {
289 diagnostics.push(Diagnostic::spanned(
290 operator.path.span(),
291 Level::Error,
292 format!("`{}` takes no generic arguments.", operator.name_string()),
293 ));
294 }
295 handoff_nodes.push((node_id, kind, operator.path.span()));
296 continue;
297 }
298
299 let Some(op_constraints) = find_op_op_constraints(operator) else {
301 diagnostics.push(Diagnostic::spanned(
302 operator.path.span(),
303 Level::Error,
304 format!("Unknown operator `{}`", operator.name_string()),
305 ));
306 continue;
307 };
308
309 let (input_ports, output_ports) = {
311 let mut input_edges: Vec<(&PortIndexValue, GraphNodeId)> = self
312 .node_predecessors(node_id)
313 .map(|(edge_id, pred_id)| (self.edge_ports(edge_id).1, pred_id))
314 .collect();
315 input_edges.sort();
317 let input_ports: Vec<PortIndexValue> = input_edges
318 .into_iter()
319 .map(|(port, _pred)| port)
320 .cloned()
321 .collect();
322
323 let mut output_edges: Vec<(&PortIndexValue, GraphNodeId)> = self
325 .node_successors(node_id)
326 .map(|(edge_id, succ)| (self.edge_ports(edge_id).0, succ))
327 .collect();
328 output_edges.sort();
330 let output_ports: Vec<PortIndexValue> = output_edges
331 .into_iter()
332 .map(|(port, _succ)| port)
333 .cloned()
334 .collect();
335
336 (input_ports, output_ports)
337 };
338
339 let generics = get_operator_generics(diagnostics, operator);
341 {
343 let generics_span = generics
345 .generic_args
346 .as_ref()
347 .map(Spanned::span)
348 .unwrap_or_else(|| operator.path.span());
349
350 if !op_constraints
351 .persistence_args
352 .contains(&generics.persistence_args.len())
353 {
354 diagnostics.push(Diagnostic::spanned(
355 generics.persistence_args_span().unwrap_or(generics_span),
356 Level::Error,
357 format!(
358 "`{}` should have {} persistence lifetime arguments, actually has {}.",
359 op_constraints.name,
360 op_constraints.persistence_args.human_string(),
361 generics.persistence_args.len()
362 ),
363 ));
364 }
365 if !op_constraints.type_args.contains(&generics.type_args.len()) {
366 diagnostics.push(Diagnostic::spanned(
367 generics.type_args_span().unwrap_or(generics_span),
368 Level::Error,
369 format!(
370 "`{}` should have {} generic type arguments, actually has {}.",
371 op_constraints.name,
372 op_constraints.type_args.human_string(),
373 generics.type_args.len()
374 ),
375 ));
376 }
377 }
378
379 op_insts.push((
380 node_id,
381 OperatorInstance {
382 op_constraints,
383 input_ports,
384 output_ports,
385 singletons_referenced: operator.singletons_referenced.clone(),
386 generics,
387 arguments_pre: operator.args.clone(),
388 arguments_raw: operator.args_raw.clone(),
389 },
390 ));
391 }
392
393 for (node_id, op_inst) in op_insts {
394 self.insert_node_op_inst(node_id, op_inst);
395 }
396
397 for (node_id, kind, span) in handoff_nodes {
399 self.nodes[node_id] = GraphNode::Handoff {
400 kind,
401 src_span: span,
402 dst_span: span,
403 };
404 }
405 }
406
407 pub fn insert_intermediate_node(
419 &mut self,
420 edge_id: GraphEdgeId,
421 new_node: GraphNode,
422 ) -> (GraphNodeId, GraphEdgeId) {
423 let span = Some(new_node.span());
424
425 let op_inst_opt = 'oc: {
427 let GraphNode::Operator(operator) = &new_node else {
428 break 'oc None;
429 };
430 let Some(op_constraints) = find_op_op_constraints(operator) else {
431 break 'oc None;
432 };
433 let (input_port, output_port) = self.ports.get(edge_id).cloned().unwrap();
434
435 let mut dummy_diagnostics = Diagnostics::new();
436 let generics = get_operator_generics(&mut dummy_diagnostics, operator);
437 assert!(dummy_diagnostics.is_empty());
438
439 Some(OperatorInstance {
440 op_constraints,
441 input_ports: vec![input_port],
442 output_ports: vec![output_port],
443 singletons_referenced: operator.singletons_referenced.clone(),
444 generics,
445 arguments_pre: operator.args.clone(),
446 arguments_raw: operator.args_raw.clone(),
447 })
448 };
449
450 let node_id = self.nodes.insert(new_node);
452 if let Some(op_inst) = op_inst_opt {
454 self.operator_instances.insert(node_id, op_inst);
455 }
456 let (e0, e1) = self
458 .graph
459 .insert_intermediate_vertex(node_id, edge_id)
460 .unwrap();
461
462 let (src_idx, dst_idx) = self.ports.remove(edge_id).unwrap();
464 self.ports
465 .insert(e0, (src_idx, PortIndexValue::Elided(span)));
466 self.ports
467 .insert(e1, (PortIndexValue::Elided(span), dst_idx));
468
469 (node_id, e1)
470 }
471
472 pub fn remove_intermediate_node(&mut self, node_id: GraphNodeId) {
475 assert_eq!(
476 1,
477 self.node_degree_in(node_id),
478 "Removed intermediate node must have one predecessor"
479 );
480 assert_eq!(
481 1,
482 self.node_degree_out(node_id),
483 "Removed intermediate node must have one successor"
484 );
485 assert!(
486 self.node_subgraph.is_empty() && self.subgraph_nodes.is_empty(),
487 "Should not remove intermediate node after subgraph partitioning"
488 );
489
490 assert!(self.nodes.remove(node_id).is_some());
491 let (new_edge_id, (pred_edge_id, succ_edge_id)) =
492 self.graph.remove_intermediate_vertex(node_id).unwrap();
493 self.operator_instances.remove(node_id);
494 self.node_varnames.remove(node_id);
495
496 let (src_port, _) = self.ports.remove(pred_edge_id).unwrap();
497 let (_, dst_port) = self.ports.remove(succ_edge_id).unwrap();
498 self.ports.insert(new_edge_id, (src_port, dst_port));
499 }
500
501 pub(crate) fn node_color(&self, node_id: GraphNodeId) -> Option<Color> {
507 if matches!(self.node(node_id), GraphNode::Handoff { .. }) {
508 return Some(Color::Hoff);
509 }
510
511 if let GraphNode::Operator(op) = self.node(node_id)
513 && (op.name_string() == "resolve_futures_blocking"
514 || op.name_string() == "resolve_futures_blocking_ordered")
515 {
516 return Some(Color::Push);
517 }
518
519 let inn_degree = self.node_predecessor_nodes(node_id).len();
521 let out_degree = self.node_successor_nodes(node_id).len();
523
524 match (inn_degree, out_degree) {
525 (0, 0) => None, (0, 1) => Some(Color::Pull),
527 (1, 0) => Some(Color::Push),
528 (1, 1) => None, (_many, 0 | 1) => Some(Color::Pull),
530 (0 | 1, _many) => Some(Color::Push),
531 (_many, _to_many) => Some(Color::Comp),
532 }
533 }
534
535 pub fn set_operator_tag(&mut self, node_id: GraphNodeId, tag: String) {
537 self.operator_tag.insert(node_id, tag);
538 }
539}
540
541impl DfirGraph {
543 pub fn set_node_handoff_references(
546 &mut self,
547 node_id: GraphNodeId,
548 singletons_referenced: Vec<ResolvedHandoffRef>,
549 ) -> Option<Vec<ResolvedHandoffRef>> {
550 self.node_handoff_references
551 .insert(node_id, singletons_referenced)
552 }
553
554 pub fn node_handoff_references(&self, node_id: GraphNodeId) -> &[ResolvedHandoffRef] {
557 self.node_handoff_references
558 .get(node_id)
559 .map(std::ops::Deref::deref)
560 .unwrap_or_default()
561 }
562
563 pub fn node_handoff_reference_groups(&self) -> NodeHandoffReferenceGroups<'_> {
565 let mut handoff_references = NodeHandoffReferenceGroups::new();
566 for node_id in self.node_ids() {
567 if let GraphNode::Operator(operator) = self.node(node_id) {
568 let resolved = self.node_handoff_references(node_id);
569 for (resolved_ref, ref_token) in
570 resolved.iter().zip(operator.singletons_referenced.iter())
571 {
572 if let Some(target_nid) = resolved_ref.node_id {
573 handoff_references
574 .entry(target_nid)
575 .or_default()
576 .entry(resolved_ref.access_group)
577 .or_default()
578 .push((node_id, resolved_ref, ref_token.span()));
579 }
580 }
581 }
582 }
583 handoff_references
584 }
585}
586
587pub type NodeHandoffReferenceGroups<'a> =
590 BTreeMap<GraphNodeId, BTreeMap<Option<u32>, Vec<(GraphNodeId, &'a ResolvedHandoffRef, Span)>>>;
591
592impl DfirGraph {
594 pub fn merge_modules(&mut self) -> Result<(), Diagnostic> {
606 let mod_bound_nodes = self
607 .nodes()
608 .filter(|(_nid, node)| matches!(node, GraphNode::ModuleBoundary { .. }))
609 .map(|(nid, _node)| nid)
610 .collect::<Vec<_>>();
611
612 for mod_bound_node in mod_bound_nodes {
613 self.remove_module_boundary(mod_bound_node)?;
614 }
615
616 Ok(())
617 }
618
619 fn remove_module_boundary(&mut self, mod_bound_node: GraphNodeId) -> Result<(), Diagnostic> {
623 assert!(
624 self.node_subgraph.is_empty() && self.subgraph_nodes.is_empty(),
625 "Should not remove intermediate node after subgraph partitioning"
626 );
627
628 let mut mod_pred_ports = BTreeMap::new();
629 let mut mod_succ_ports = BTreeMap::new();
630
631 for mod_out_edge in self.node_predecessor_edges(mod_bound_node) {
632 let (pred_port, succ_port) = self.edge_ports(mod_out_edge);
633 mod_pred_ports.insert(succ_port.clone(), (mod_out_edge, pred_port.clone()));
634 }
635
636 for mod_inn_edge in self.node_successor_edges(mod_bound_node) {
637 let (pred_port, succ_port) = self.edge_ports(mod_inn_edge);
638 mod_succ_ports.insert(pred_port.clone(), (mod_inn_edge, succ_port.clone()));
639 }
640
641 if mod_pred_ports.keys().collect::<BTreeSet<_>>()
642 != mod_succ_ports.keys().collect::<BTreeSet<_>>()
643 {
644 let GraphNode::ModuleBoundary { input, import_expr } = self.node(mod_bound_node) else {
646 panic!();
647 };
648
649 if *input {
650 return Err(Diagnostic {
651 span: *import_expr,
652 level: Level::Error,
653 message: format!(
654 "The ports into the module did not match. input: {:?}, expected: {:?}",
655 mod_pred_ports.keys().map(|x| x.to_string()).join(", "),
656 mod_succ_ports.keys().map(|x| x.to_string()).join(", ")
657 ),
658 });
659 }
660 return Err(Diagnostic {
661 span: *import_expr,
662 level: Level::Error,
663 message: format!(
664 "The ports out of the module did not match. output: {:?}, expected: {:?}",
665 mod_succ_ports.keys().map(|x| x.to_string()).join(", "),
666 mod_pred_ports.keys().map(|x| x.to_string()).join(", "),
667 ),
668 });
669 }
670
671 for (port, (pred_edge, pred_port)) in mod_pred_ports {
672 let (succ_edge, succ_port) = mod_succ_ports.remove(&port).unwrap();
673
674 let (src, _) = self.edge(pred_edge);
675 let (_, dst) = self.edge(succ_edge);
676 self.remove_edge(pred_edge);
677 self.remove_edge(succ_edge);
678
679 let new_edge_id = self.graph.insert_edge(src, dst);
680 self.ports.insert(new_edge_id, (pred_port, succ_port));
681 }
682
683 self.graph.remove_vertex(mod_bound_node);
684 self.nodes.remove(mod_bound_node);
685
686 Ok(())
687 }
688}
689
690impl DfirGraph {
692 pub fn edge(&self, edge_id: GraphEdgeId) -> (GraphNodeId, GraphNodeId) {
694 let (src, dst) = self.graph.edge(edge_id).expect("Edge not found.");
695 (src, dst)
696 }
697
698 pub fn edge_ports(&self, edge_id: GraphEdgeId) -> (&PortIndexValue, &PortIndexValue) {
700 let (src_port, dst_port) = self.ports.get(edge_id).expect("Edge not found.");
701 (src_port, dst_port)
702 }
703
704 pub fn edge_ids(&self) -> slotmap::basic::Keys<'_, GraphEdgeId, (GraphNodeId, GraphNodeId)> {
706 self.graph.edge_ids()
707 }
708
709 pub fn edges(
711 &self,
712 ) -> impl '_
713 + ExactSizeIterator<Item = (GraphEdgeId, (GraphNodeId, GraphNodeId))>
714 + FusedIterator
715 + Clone
716 + Debug {
717 self.graph.edges()
718 }
719
720 pub fn insert_edge(
722 &mut self,
723 src: GraphNodeId,
724 src_port: PortIndexValue,
725 dst: GraphNodeId,
726 dst_port: PortIndexValue,
727 ) -> GraphEdgeId {
728 let edge_id = self.graph.insert_edge(src, dst);
729 self.ports.insert(edge_id, (src_port, dst_port));
730 edge_id
731 }
732
733 pub fn remove_edge(&mut self, edge: GraphEdgeId) {
735 let (_src, _dst) = self.graph.remove_edge(edge).unwrap();
736 let (_src_port, _dst_port) = self.ports.remove(edge).unwrap();
737 }
738}
739
740impl DfirGraph {
742 pub fn subgraph(&self, subgraph_id: GraphSubgraphId) -> &Vec<GraphNodeId> {
744 self.subgraph_nodes
745 .get(subgraph_id)
746 .expect("Subgraph not found.")
747 }
748
749 pub fn subgraph_ids(&self) -> slotmap::basic::Keys<'_, GraphSubgraphId, Vec<GraphNodeId>> {
751 self.subgraph_nodes.keys()
752 }
753
754 pub fn subgraph_toposort(&self) -> &[GraphSubgraphId] {
756 &self.subgraph_toposort
757 }
758
759 pub fn set_subgraph_toposort(&mut self, order: Vec<GraphSubgraphId>) {
761 self.subgraph_toposort = order;
762 }
763
764 pub fn subgraphs(&self) -> slotmap::basic::Iter<'_, GraphSubgraphId, Vec<GraphNodeId>> {
766 self.subgraph_nodes.iter()
767 }
768
769 pub fn insert_subgraph(
771 &mut self,
772 node_ids: Vec<GraphNodeId>,
773 ) -> Result<GraphSubgraphId, (GraphNodeId, GraphSubgraphId)> {
774 for &node_id in node_ids.iter() {
776 if let Some(&old_sg_id) = self.node_subgraph.get(node_id) {
777 return Err((node_id, old_sg_id));
778 }
779 }
780 let subgraph_id = self.subgraph_nodes.insert_with_key(|sg_id| {
781 for &node_id in node_ids.iter() {
782 self.node_subgraph.insert(node_id, sg_id);
783 }
784 node_ids
785 });
786
787 Ok(subgraph_id)
788 }
789
790 pub fn remove_from_subgraph(&mut self, node_id: GraphNodeId) -> bool {
792 if let Some(old_sg_id) = self.node_subgraph.remove(node_id) {
793 self.subgraph_nodes[old_sg_id].retain(|&other_node_id| other_node_id != node_id);
794 true
795 } else {
796 false
797 }
798 }
799
800 pub fn handoff_delay_type(&self, node_id: GraphNodeId) -> Option<DelayType> {
802 self.handoff_delay_type.get(node_id).copied()
803 }
804
805 pub fn set_handoff_delay_type(&mut self, node_id: GraphNodeId, delay_type: DelayType) {
807 self.handoff_delay_type.insert(node_id, delay_type);
808 }
809
810 fn find_pull_to_push_idx(&self, subgraph_nodes: &[GraphNodeId]) -> usize {
812 subgraph_nodes
813 .iter()
814 .position(|&node_id| {
815 self.node_color(node_id)
816 .is_some_and(|color| Color::Pull != color)
817 })
818 .unwrap_or(subgraph_nodes.len())
819 }
820}
821
822impl DfirGraph {
824 fn node_as_ident(&self, node_id: GraphNodeId, is_pred: bool) -> Ident {
826 let name = match &self.nodes[node_id] {
827 GraphNode::Operator(_) => format!("op_{:?}", node_id.data()),
828 GraphNode::Handoff {
829 kind: HandoffKind::Vec,
830 ..
831 } => format!(
832 "hoff_{:?}_{}",
833 node_id.data(),
834 if is_pred { "recv" } else { "send" }
835 ),
836 GraphNode::Handoff {
837 kind: HandoffKind::Singleton | HandoffKind::Optional,
838 ..
839 } => format!(
840 "singleton_{:?}_{}",
841 node_id.data(),
842 if is_pred { "recv" } else { "send" }
843 ),
844 GraphNode::ModuleBoundary { .. } => panic!(),
845 };
846 let span = match (is_pred, &self.nodes[node_id]) {
847 (_, GraphNode::Operator(operator)) => operator.span(),
848 (true, &GraphNode::Handoff { src_span, .. }) => src_span,
849 (false, &GraphNode::Handoff { dst_span, .. }) => dst_span,
850 (_, GraphNode::ModuleBoundary { .. }) => panic!(),
851 };
852 Ident::new(&name, span)
853 }
854
855 fn hoff_buf_ident(&self, hoff_id: GraphNodeId, span: Span) -> Ident {
857 Ident::new(&format!("hoff_{:?}_buf", hoff_id.data()), span)
858 }
859
860 fn hoff_back_ident(&self, hoff_id: GraphNodeId, span: Span) -> Ident {
862 Ident::new(&format!("hoff_{:?}_back", hoff_id.data()), span)
863 }
864
865 fn helper_resolve_singletons(&self, node_id: GraphNodeId, span: Span) -> Vec<TokenStream> {
874 self.node_handoff_references(node_id)
875 .iter()
876 .map(|resolved_ref| {
877 let ref_node_id = resolved_ref
879 .node_id
880 .expect("Expected singleton to be resolved but was not, this is a bug.");
881 let is_mut = resolved_ref.is_mut;
882 match self.node(ref_node_id) {
883 GraphNode::Handoff {
884 kind: HandoffKind::Singleton,
885 ..
886 } => {
887 let buf_ident = self.hoff_buf_ident(ref_node_id, span);
888 if is_mut {
889 quote_spanned! {span=> #buf_ident.as_mut().unwrap() }
890 } else {
891 quote_spanned! {span=> #buf_ident.as_ref().unwrap() }
892 }
893 }
894 GraphNode::Handoff {
895 kind: HandoffKind::Optional | HandoffKind::Vec,
896 ..
897 } => {
898 let buf_ident = self.hoff_buf_ident(ref_node_id, span);
899 if is_mut {
900 quote_spanned! {span=> &mut #buf_ident }
901 } else {
902 quote_spanned! {span=> &#buf_ident }
903 }
904 }
905 _ => {
906 unreachable!("Only handoff nodes should be reachable as handoff references")
907 }
908 }
909 })
910 .collect::<Vec<_>>()
911 }
912
913 fn helper_collect_subgraph_handoffs(
916 &self,
917 ) -> SecondaryMap<GraphSubgraphId, (Vec<GraphNodeId>, Vec<GraphNodeId>)> {
918 let mut subgraph_handoffs: SecondaryMap<
920 GraphSubgraphId,
921 (Vec<GraphNodeId>, Vec<GraphNodeId>),
922 > = self
923 .subgraph_nodes
924 .keys()
925 .map(|k| (k, Default::default()))
926 .collect();
927
928 for (hoff_id, hoff) in self.nodes() {
930 if !matches!(hoff, GraphNode::Handoff { .. }) {
931 continue;
932 }
933 for (_edge, succ_id) in self.node_successors(hoff_id) {
935 let succ_sg = self
936 .node_subgraph(succ_id)
937 .expect("bug: successor not in subgraph, may be a doubled/adjacent handoff");
938 subgraph_handoffs[succ_sg].0.push(hoff_id);
939 }
940 for (_edge, pred_id) in self.node_predecessors(hoff_id) {
942 let pred_sg = self
943 .node_subgraph(pred_id)
944 .expect("bug: predecessor not in subgraph, may be a doubled/adjacent handoff");
945 subgraph_handoffs[pred_sg].1.push(hoff_id);
946 }
947 }
948
949 subgraph_handoffs
950 }
951
952 fn helper_loop_output_handoffs(&self) -> SecondaryMap<GraphLoopId, Vec<GraphNodeId>> {
955 let mut loop_hoffs_out = SecondaryMap::<GraphLoopId, Vec<GraphNodeId>>::new();
956
957 for (hoff_id, hoff) in self.nodes() {
958 if !matches!(hoff, GraphNode::Handoff { .. }) {
959 continue;
960 }
961
962 let loop_pred = self
963 .node_predecessors(hoff_id)
964 .next()
965 .and_then(|(_, pred)| self.node_loop(pred));
966 let loop_succ = self
967 .node_successors(hoff_id)
968 .next()
969 .and_then(|(_, succ)| self.node_loop(succ));
970
971 if let Some(loop_pred) = loop_pred
972 && loop_succ == self.loop_parent(loop_pred)
973 {
974 loop_hoffs_out
976 .entry(loop_pred)
977 .expect("loop removed")
978 .or_default()
979 .push(hoff_id);
980 }
981 }
982
983 loop_hoffs_out
984 }
985
986 fn is_inside_loop(&self, node_loop: Option<GraphLoopId>, loop_id: GraphLoopId) -> bool {
988 let mut current = node_loop;
989 while let Some(l) = current {
990 if l == loop_id {
991 return true;
992 }
993 current = self.loop_parent(l);
994 }
995 false
996 }
997
998 fn emit_loop_gate(
1007 &self,
1008 loop_id: GraphLoopId,
1009 child_body: TokenStream,
1010 loop_input_handoffs: &SecondaryMap<GraphLoopId, Vec<GraphNodeId>>,
1011 back_edge_hoffs_and_lazyness: &SparseSecondaryMap<GraphNodeId, bool>,
1012 loop_swap_code: &std::collections::HashMap<GraphLoopId, Vec<TokenStream>>,
1013 output: &mut TokenStream,
1014 ) {
1015 let swap_code = loop_swap_code
1017 .get(&loop_id)
1018 .map(|v| v.as_slice())
1019 .unwrap_or(&[]);
1020
1021 let is_root_loop = self.loop_parent(loop_id).is_none();
1023
1024 let entry_handoffs = loop_input_handoffs.get(loop_id).expect("loop missing");
1026 let mut gate_checks: Vec<TokenStream> = entry_handoffs
1027 .iter()
1028 .filter(|&&hoff_id| {
1029 let is_lazy = self
1032 .node_successors(hoff_id)
1033 .next()
1034 .and_then(|(_, succ)| self.node_op_inst(succ))
1035 .is_some_and(|op_inst| {
1036 op_inst.op_constraints.flo_type == Some(FloType::WindowingLazy)
1037 });
1038 !is_lazy
1039 })
1040 .map(|&hoff_id| {
1041 let span = self.node(hoff_id).span();
1042 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1043 if back_edge_hoffs_and_lazyness.contains_key(hoff_id) {
1044 let back_ident = self.hoff_back_ident(hoff_id, span);
1045 quote_spanned! {span=> !#back_ident.is_empty() }
1046 } else {
1047 quote_spanned! {span=> !#buf_ident.is_empty() }
1048 }
1049 })
1050 .collect();
1051
1052 if !is_root_loop {
1054 for (hoff_id, hoff) in self.nodes() {
1055 if !matches!(hoff, GraphNode::Handoff { .. }) {
1056 continue;
1057 }
1058 let Some(delay_type) = self.handoff_delay_type(hoff_id) else {
1059 continue;
1060 };
1061 if delay_type != DelayType::Loop {
1062 continue;
1063 }
1064 let hoff_loop = self
1066 .node_successors(hoff_id)
1067 .next()
1068 .and_then(|(_, succ)| self.node_subgraph(succ))
1069 .and_then(|sg| self.subgraph_loop(sg));
1070 if hoff_loop != Some(loop_id) {
1071 continue;
1072 }
1073 let span = self.node(hoff_id).span();
1074 let back_ident = self.hoff_back_ident(hoff_id, span);
1075 gate_checks.push(quote_spanned! {span=> !#back_ident.is_empty() });
1076 }
1077 }
1078
1079 if is_root_loop {
1082 for (hoff_id, hoff) in self.nodes() {
1083 if !matches!(hoff, GraphNode::Handoff { .. }) {
1084 continue;
1085 }
1086 let Some(delay_type) = self.handoff_delay_type(hoff_id) else {
1087 continue;
1088 };
1089 if delay_type != DelayType::Tick {
1090 continue;
1091 }
1092 let hoff_loop = self
1094 .node_successors(hoff_id)
1095 .next()
1096 .and_then(|(_, succ)| self.node_subgraph(succ))
1097 .and_then(|sg| self.subgraph_loop(sg));
1098 if hoff_loop != Some(loop_id) {
1099 continue;
1100 }
1101 let span = self.node(hoff_id).span();
1102 let back_ident = self.hoff_back_ident(hoff_id, span);
1103 gate_checks.push(quote_spanned! {span=> !#back_ident.is_empty() });
1104 }
1105 }
1106
1107 let has_eager = entry_handoffs.iter().any(|&hoff_id| {
1112 self.node_successors(hoff_id)
1113 .next()
1114 .and_then(|(_, succ)| self.node_op_inst(succ))
1115 .is_some_and(|op_inst| {
1116 op_inst.op_constraints.flo_type == Some(FloType::WindowingEager)
1117 })
1118 });
1119
1120 if has_eager && is_root_loop {
1121 output.extend(child_body);
1123 output.extend(quote! { #( #swap_code )* });
1124 } else if gate_checks.is_empty() {
1125 output.extend(child_body);
1127 output.extend(quote! { #( #swap_code )* });
1128 } else if is_root_loop {
1129 output.extend(quote! {
1131 #[allow(clippy::nonminimal_bool, reason = "codegen")]
1132 if false #( || #gate_checks )* {
1133 #child_body
1134 #( #swap_code )*
1135 }
1136 });
1137 } else {
1138 output.extend(quote! {
1140 #[allow(clippy::nonminimal_bool, reason = "codegen")]
1141 while false #( || #gate_checks )* {
1142 #child_body
1143 #( #swap_code )*
1144 }
1145 });
1146 }
1147 }
1148
1149 fn helper_loop_input_handoffs(&self) -> SecondaryMap<GraphLoopId, Vec<GraphNodeId>> {
1151 let mut loop_hoffs_inn = SecondaryMap::<GraphLoopId, Vec<GraphNodeId>>::new();
1152
1153 for (hoff_id, hoff) in self.nodes() {
1155 if !matches!(hoff, GraphNode::Handoff { .. }) {
1156 continue;
1157 }
1158
1159 let loop_pred = self
1161 .node_predecessors(hoff_id)
1162 .next()
1163 .and_then(|(_, pred)| self.node_loop(pred));
1164 let loop_succ = self
1165 .node_successors(hoff_id)
1166 .next()
1167 .and_then(|(_, succ)| self.node_loop(succ));
1168
1169 if let Some(loop_succ) = loop_succ
1170 && loop_pred == self.loop_parent(loop_succ)
1171 {
1172 loop_hoffs_inn
1174 .entry(loop_succ)
1175 .expect("loop removed")
1176 .or_default()
1177 .push(hoff_id);
1178 }
1179 }
1180
1181 loop_hoffs_inn
1182 }
1183
1184 pub fn as_code(
1203 &self,
1204 root: &TokenStream,
1205 include_type_guards: bool,
1206 prefix: TokenStream,
1207 diagnostics: &mut Diagnostics,
1208 ) -> Result<TokenStream, Diagnostics> {
1209 self.as_code_with_options(
1210 root,
1211 &AsCodeOptions {
1212 exclude_type_guards: !include_type_guards,
1213 ..Default::default()
1214 },
1215 prefix,
1216 diagnostics,
1217 )
1218 }
1219
1220 pub fn as_code_with_options(
1227 &self,
1228 root: &TokenStream,
1229 options: &AsCodeOptions,
1230 prefix: TokenStream,
1231 diagnostics: &mut Diagnostics,
1232 ) -> Result<TokenStream, Diagnostics> {
1233 let df = Ident::new(GRAPH, Span::call_site());
1234 let context = Ident::new(CONTEXT, Span::call_site());
1235 let bump_ident = Ident::new("__dfir_bump", Span::call_site());
1237
1238 let handoff_nodes = self
1240 .nodes
1241 .iter()
1242 .filter_map(|(node_id, node)| match node {
1243 &GraphNode::Handoff {
1244 kind,
1245 src_span,
1246 dst_span,
1247 } => Some((node_id, kind, (src_span, dst_span))),
1248 GraphNode::Operator(_) => None,
1249 GraphNode::ModuleBoundary { .. } => panic!(),
1250 })
1251 .collect::<Vec<_>>();
1252
1253 let back_edge_hoffs_and_lazyness = handoff_nodes
1257 .iter()
1258 .map(|&(node_id, _, _)| node_id)
1259 .filter_map(|node_id| {
1260 let delay_type = self.handoff_delay_type(node_id)?;
1261 Some((
1262 node_id,
1263 matches!(delay_type, DelayType::TickLazy | DelayType::LoopLazy),
1264 ))
1265 })
1266 .collect::<SparseSecondaryMap<_, _>>();
1267
1268 let back_buffer_idents_laziness = handoff_nodes
1270 .iter()
1271 .filter_map(|&(hoff_id, _kind, (src_span, dst_span))| {
1272 back_edge_hoffs_and_lazyness.get(hoff_id).map(|&is_lazy| {
1273 let span = src_span.join(dst_span).unwrap_or(src_span);
1274 let back_ident = self.hoff_back_ident(hoff_id, span);
1275 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1276 (back_ident, buf_ident, is_lazy)
1277 })
1278 })
1279 .collect::<Vec<_>>();
1280
1281 let back_edge_swap_code = handoff_nodes
1288 .iter()
1289 .filter(|&&(node_id, _kind, _)| {
1290 self.handoff_delay_type(node_id)
1291 .is_some_and(|dt| matches!(dt, DelayType::Tick | DelayType::TickLazy))
1292 })
1293 .filter(|&&(hoff_id, _kind, _)| {
1294 let consumer_loop = self
1297 .node_successors(hoff_id)
1298 .next()
1299 .and_then(|(_, succ)| self.node_subgraph(succ))
1300 .and_then(|sg| self.subgraph_loop(sg));
1301 if let Some(loop_id) = consumer_loop {
1302 self.loop_parent(loop_id).is_some()
1304 } else {
1305 true
1307 }
1308 })
1309 .map(|&(hoff_id, _kind, _)| {
1310 let span = self.nodes[hoff_id].span();
1311 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1312 let back_ident = self.hoff_back_ident(hoff_id, span);
1313 quote_spanned! {span=>
1314 ::std::mem::swap(&mut #buf_ident, &mut #back_ident);
1315 }
1316 })
1317 .collect::<Vec<_>>();
1318
1319 let mut loop_swap_code: std::collections::HashMap<GraphLoopId, Vec<TokenStream>> =
1323 std::collections::HashMap::new();
1324 for &(hoff_id, _kind, _) in handoff_nodes.iter() {
1325 let Some(delay_type) = self.handoff_delay_type(hoff_id) else {
1326 continue;
1327 };
1328 let loop_id = self
1330 .node_successors(hoff_id)
1331 .next()
1332 .and_then(|(_, succ)| self.node_subgraph(succ))
1333 .and_then(|sg| self.subgraph_loop(sg));
1334 let Some(loop_id) = loop_id else {
1335 continue;
1336 };
1337 let include = match delay_type {
1338 DelayType::Loop | DelayType::LoopLazy => true,
1339 DelayType::Tick | DelayType::TickLazy => {
1340 self.loop_parent(loop_id).is_none()
1342 }
1343 };
1344 if !include {
1345 continue;
1346 }
1347 let span = self.nodes[hoff_id].span();
1348 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1349 let back_ident = self.hoff_back_ident(hoff_id, span);
1350 loop_swap_code
1351 .entry(loop_id)
1352 .or_default()
1353 .push(quote_spanned! {span=>
1354 ::std::mem::swap(&mut #buf_ident, &mut #back_ident);
1355 });
1356 }
1357
1358 let subgraph_handoffs = self.helper_collect_subgraph_handoffs();
1360
1361 let all_subgraphs: Vec<_> = self
1363 .subgraph_toposort()
1364 .iter()
1365 .map(|&sg_id| (sg_id, self.subgraph(sg_id)))
1366 .collect();
1367
1368 let mut op_prologue_code = Vec::new();
1372 let mut op_tick_end_code = Vec::new();
1373
1374 let mut loop_stack: Vec<(GraphLoopId, TokenStream)> = Vec::new();
1378 let mut current_output = TokenStream::new();
1379
1380 let loop_input_handoffs = self.helper_loop_input_handoffs();
1382 let loop_output_handoffs = self.helper_loop_output_handoffs();
1383
1384 {
1385 for &(subgraph_id, subgraph_nodes) in all_subgraphs.iter() {
1386 let sg_loop = self.subgraph_loop(subgraph_id);
1387
1388 while let Some(&(top_loop, _)) = loop_stack.last() {
1391 if sg_loop == Some(top_loop) || self.is_inside_loop(sg_loop, top_loop) {
1392 break;
1393 }
1394 let (closed_loop, child_body) = loop_stack.pop().unwrap();
1396 let target = if let Some((_, parent_body)) = loop_stack.last_mut() {
1397 parent_body
1398 } else {
1399 &mut current_output
1400 };
1401 self.emit_loop_gate(
1402 closed_loop,
1403 child_body,
1404 &loop_input_handoffs,
1405 &back_edge_hoffs_and_lazyness,
1406 &loop_swap_code,
1407 target,
1408 );
1409 }
1410
1411 if let Some(target_loop) = sg_loop
1413 && loop_stack.last().map(|&(l, _)| l) != Some(target_loop)
1414 {
1415 let mut path = Vec::new();
1417 let mut cur = Some(target_loop);
1418 while let Some(l) = cur {
1419 if loop_stack.last().map(|&(top, _)| top) == Some(l) {
1420 break;
1421 }
1422 path.push(l);
1423 cur = self.loop_parent(l);
1424 }
1425 for &loop_id in path.iter().rev() {
1428 if let Some(exit_hoffs) = loop_output_handoffs.get(loop_id) {
1430 let exit_hoff_decls = exit_hoffs.iter().map(|&hoff_id| {
1431 let span = self.nodes[hoff_id].span();
1432 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1433 let GraphNode::Handoff { kind, .. } = self.node(hoff_id) else {
1434 panic!()
1435 };
1436 match kind {
1437 HandoffKind::Vec => quote_spanned! {span=>
1438 let mut #buf_ident = #root::bumpalo::collections::Vec::new_in(&#bump_ident);
1439 },
1440 HandoffKind::Singleton | HandoffKind::Optional => quote_spanned! {span=>
1441 let mut #buf_ident = ::std::option::Option::None;
1442 },
1443 }
1444 });
1445 let target = if let Some((_, body)) = loop_stack.last_mut() {
1446 body
1447 } else {
1448 &mut current_output
1449 };
1450 target.extend(quote! { #( #exit_hoff_decls )* });
1451 }
1452 loop_stack.push((loop_id, TokenStream::new()));
1453 }
1454 }
1455 let sg_metrics_ffi = subgraph_id.data().as_ffi();
1456 let (recv_hoffs, send_hoffs) = &subgraph_handoffs[subgraph_id];
1457
1458 let recv_port_idents: Vec<Ident> = recv_hoffs
1460 .iter()
1461 .map(|&hoff_id| self.node_as_ident(hoff_id, true))
1462 .collect();
1463 let send_port_idents: Vec<Ident> = send_hoffs
1464 .iter()
1465 .map(|&hoff_id| self.node_as_ident(hoff_id, false))
1466 .collect();
1467
1468 let recv_buf_idents: Vec<Ident> = recv_hoffs
1470 .iter()
1471 .map(|&hoff_id| self.hoff_buf_ident(hoff_id, self.nodes[hoff_id].span()))
1472 .collect();
1473 let send_buf_idents: Vec<Ident> = send_hoffs
1474 .iter()
1475 .map(|&hoff_id| self.hoff_buf_ident(hoff_id, self.nodes[hoff_id].span()))
1476 .collect();
1477
1478 let recv_kinds = recv_hoffs
1480 .iter()
1481 .map(|&hoff_id| {
1482 let GraphNode::Handoff { kind, .. } = self.node(hoff_id) else {
1483 panic!()
1484 };
1485 *kind
1486 })
1487 .collect::<Vec<_>>();
1488 let send_kinds = send_hoffs
1489 .iter()
1490 .map(|&hoff_id| {
1491 let GraphNode::Handoff { kind, .. } = self.node(hoff_id) else {
1492 panic!()
1493 };
1494 *kind
1495 })
1496 .collect::<Vec<_>>();
1497
1498 let recv_port_code: Vec<TokenStream> = recv_port_idents
1502 .iter()
1503 .zip(recv_buf_idents.iter())
1504 .zip(recv_kinds.iter())
1505 .zip(recv_hoffs.iter())
1506 .map(|(((port_ident, buf_ident), &kind), &hoff_id)| {
1507 let hoff_ffi = hoff_id.data().as_ffi();
1508 let work_done = Ident::new("__dfir_work_done", Span::call_site());
1512 let metrics = Ident::new("__dfir_metrics", Span::call_site());
1513
1514 let (len_expr, drain_expr) = match kind {
1516 HandoffKind::Singleton | HandoffKind::Optional => (
1517 quote! { if #buf_ident.is_some() { 1usize } else { 0usize } },
1518 quote! { #root::dfir_pipes::pull::iter(#buf_ident.take().into_iter()) },
1519 ),
1520 HandoffKind::Vec => {
1521 let drain_ident = if back_edge_hoffs_and_lazyness.contains_key(hoff_id) {
1525 &self.hoff_back_ident(hoff_id, buf_ident.span())
1526 } else {
1527 buf_ident
1528 };
1529 (
1530 quote! { #drain_ident.len() },
1531 quote! { #root::dfir_pipes::pull::iter(#drain_ident.drain(..)) },
1532 )
1533 }
1534 };
1535
1536 let track_hoff_metrics = options.include_metrics_tracking.then(|| {
1537 quote_spanned! {port_ident.span()=>
1538 let hoff_metrics = &#metrics.handoffs[
1539 #root::slotmap::KeyData::from_ffi(#hoff_ffi).into()
1540 ];
1541 hoff_metrics.total_items_count.update(|x| x + hoff_len);
1542 hoff_metrics.curr_items_count.set(hoff_len);
1543 }
1544 });
1545
1546 quote_spanned! {port_ident.span()=>
1547 {
1548 let hoff_len = #len_expr;
1549 if hoff_len > 0 {
1550 #work_done = true;
1551 }
1552 #track_hoff_metrics
1553 }
1554 let #port_ident = #drain_expr;
1555 }
1556 })
1557 .collect();
1558
1559 let send_port_code: Vec<TokenStream> = send_port_idents
1561 .iter()
1562 .zip(send_buf_idents.iter())
1563 .zip(send_kinds.iter())
1564 .map(|((port_ident, buf_ident), &kind)| {
1565 match kind {
1566 HandoffKind::Singleton => {
1567 quote_spanned! {port_ident.span()=>
1569 let #port_ident = #root::dfir_pipes::push::for_each(|__item| {
1570 if #buf_ident.replace(__item).is_some() {
1571 panic!("singleton() received more than one item");
1572 }
1573 });
1574 }
1575 }
1576 HandoffKind::Optional => {
1577 quote_spanned! {port_ident.span()=>
1579 let #port_ident = #root::dfir_pipes::push::for_each(|__item| {
1580 if #buf_ident.replace(__item).is_some() {
1581 panic!("optional() received more than one item");
1582 }
1583 });
1584 }
1585 }
1586 HandoffKind::Vec => {
1587 quote_spanned! {port_ident.span()=>
1588 let #port_ident = #root::dfir_pipes::push::for_each(|item| { #buf_ident.push(item); });
1590 }
1591 }
1592 }
1593 })
1594 .collect();
1595
1596 let loop_id = self.node_loop(subgraph_nodes[0]);
1598
1599 let mut subgraph_op_iter_code = Vec::new();
1600 let mut subgraph_op_iter_after_code = Vec::new();
1601 {
1602 let pull_to_push_idx = self.find_pull_to_push_idx(subgraph_nodes);
1603
1604 let (pull_half, push_half) = subgraph_nodes.split_at(pull_to_push_idx);
1605 let nodes_iter = pull_half.iter().chain(push_half.iter().rev());
1606
1607 for (idx, &node_id) in nodes_iter.enumerate() {
1608 let node = &self.nodes[node_id];
1609 assert!(
1610 matches!(node, GraphNode::Operator(_)),
1611 "Handoffs are not part of subgraphs."
1612 );
1613 let op_inst = &self.operator_instances[node_id];
1614
1615 let op_span = node.span();
1616 let op_name = op_inst.op_constraints.name;
1617 let root = change_spans(root.clone(), op_span);
1619 let op_constraints = OPERATORS
1620 .iter()
1621 .find(|op| op_name == op.name)
1622 .unwrap_or_else(|| panic!("Failed to find op: {}", op_name));
1623
1624 let ident = self.node_as_ident(node_id, false);
1625
1626 {
1627 let mut input_edges = self
1630 .graph
1631 .predecessor_edges(node_id)
1632 .map(|edge_id| (self.edge_ports(edge_id).1, edge_id))
1633 .collect::<Vec<_>>();
1634 input_edges.sort();
1636
1637 let inputs = input_edges
1638 .iter()
1639 .map(|&(_port, edge_id)| {
1640 let (pred, _) = self.edge(edge_id);
1641 self.node_as_ident(pred, true)
1642 })
1643 .collect::<Vec<_>>();
1644
1645 let mut output_edges = self
1647 .graph
1648 .successor_edges(node_id)
1649 .map(|edge_id| (&self.ports[edge_id].0, edge_id))
1650 .collect::<Vec<_>>();
1651 output_edges.sort();
1653
1654 let outputs = output_edges
1655 .iter()
1656 .map(|&(_port, edge_id)| {
1657 let (_, succ) = self.edge(edge_id);
1658 self.node_as_ident(succ, false)
1659 })
1660 .collect::<Vec<_>>();
1661
1662 let is_pull = idx < pull_to_push_idx;
1663
1664 let df_local = &Ident::new(GRAPH, op_span.resolved_at(df.span()));
1673 let context = &Ident::new(CONTEXT, op_span.resolved_at(context.span()));
1674
1675 let singletons_resolved =
1676 self.helper_resolve_singletons(node_id, op_span);
1677
1678 let arguments = &process_singletons::postprocess_singletons(
1679 op_inst.arguments_raw.clone(),
1680 singletons_resolved,
1681 );
1682
1683 let source_tag = 'a: {
1684 if let Some(tag) = self.operator_tag.get(node_id).cloned() {
1685 break 'a tag;
1686 }
1687
1688 if proc_macro::is_available() {
1689 let op_span = op_span.unwrap();
1690 break 'a format!(
1691 "loc_{}_{}_{}_{}_{}",
1692 crate::pretty_span::make_source_path_relative(
1693 &op_span.file()
1694 )
1695 .display()
1696 .to_string()
1697 .replace(|x: char| !x.is_ascii_alphanumeric(), "_"),
1698 op_span.start().line(),
1699 op_span.start().column(),
1700 op_span.end().line(),
1701 op_span.end().column(),
1702 );
1703 }
1704
1705 format!(
1706 "loc_nopath_{}_{}_{}_{}",
1707 op_span.start().line,
1708 op_span.start().column,
1709 op_span.end().line,
1710 op_span.end().column
1711 )
1712 };
1713
1714 let work_fn = format_ident!(
1715 "{}__{}__{}",
1716 ident,
1717 op_name,
1718 source_tag,
1719 span = op_span
1720 );
1721 let work_fn_async = format_ident!("{}__async", work_fn, span = op_span);
1722
1723 let context_args = WriteContextArgs {
1724 root: &root,
1725 df_ident: df_local,
1726 context,
1727 subgraph_id,
1728 node_id,
1729 loop_id,
1730 op_span,
1731 op_tag: self.operator_tag.get(node_id).cloned(),
1732 work_fn: &work_fn,
1733 work_fn_async: &work_fn_async,
1734 ident: &ident,
1735 is_pull,
1736 inputs: &inputs,
1737 outputs: &outputs,
1738 op_name,
1739 op_inst,
1740 arguments,
1741 options,
1742 };
1743
1744 let write_result =
1745 (op_constraints.write_fn)(&context_args, diagnostics);
1746 let OperatorWriteOutput {
1747 write_prologue,
1748 write_iterator,
1749 write_iterator_after,
1750 write_tick_end,
1751 } = write_result.unwrap_or_else(|()| {
1752 assert!(
1753 diagnostics.has_error(),
1754 "Operator `{}` returned `Err` but emitted no diagnostics, this is a bug.",
1755 op_name,
1756 );
1757 OperatorWriteOutput {
1758 write_prologue: Default::default(),
1759 write_iterator: null_write_iterator_fn(&context_args),
1760 write_iterator_after: Default::default(),
1761 write_tick_end: Default::default(),
1762 }
1763 });
1764
1765 let op_outputs = [
1771 &write_prologue,
1772 &write_iterator,
1773 &write_iterator_after,
1774 &write_tick_end,
1775 ];
1776 let uses_ident = |ident: &Ident| {
1777 op_outputs
1778 .iter()
1779 .any(|tokens| tokens_contain_ident((*tokens).clone(), ident))
1780 };
1781
1782 if uses_ident(&work_fn) {
1783 op_prologue_code.push(syn::parse_quote! {
1784 #[allow(dead_code, non_snake_case, reason = "codegen")]
1785 #[inline(always)]
1786 fn #work_fn<T>(thunk: impl ::std::ops::FnOnce() -> T) -> T {
1787 thunk()
1788 }
1789 });
1790 }
1791 if uses_ident(&work_fn_async) {
1792 op_prologue_code.push(syn::parse_quote! {
1793 #[allow(dead_code, non_snake_case, reason = "codegen")]
1794 #[inline(always)]
1795 async fn #work_fn_async<T>(
1796 thunk: impl ::std::future::Future<Output = T>,
1797 ) -> T {
1798 thunk.await
1799 }
1800 });
1801 }
1802 op_prologue_code.push(write_prologue);
1803 op_tick_end_code.push(write_tick_end);
1804 subgraph_op_iter_code.push(write_iterator);
1805
1806 if !options.exclude_type_guards || options.require_flame_attribution {
1811 let type_guard = if is_pull {
1812 quote_spanned! {op_span=>
1813 let #ident = {
1814 #[allow(non_snake_case)]
1815 #[inline(always)]
1816 pub fn #work_fn<Item, Input>(input: Input)
1817 -> impl #root::dfir_pipes::pull::Pull<Item = Item, Meta = (), CanPend = Input::CanPend, CanEnd = Input::CanEnd>
1818 where
1819 Input: #root::dfir_pipes::pull::Pull<Item = Item, Meta = ()>,
1820 {
1821 #root::pin_project_lite::pin_project! {
1822 #[repr(transparent)]
1823 struct Pull<Item, Input: #root::dfir_pipes::pull::Pull<Item = Item>> {
1824 #[pin]
1825 inner: Input
1826 }
1827 }
1828
1829 impl<Item, Input> #root::dfir_pipes::pull::Pull for Pull<Item, Input>
1830 where
1831 Input: #root::dfir_pipes::pull::Pull<Item = Item>,
1832 {
1833 type Ctx<'ctx> = Input::Ctx<'ctx>;
1834
1835 type Item = Item;
1836 type Meta = Input::Meta;
1837 type CanPend = Input::CanPend;
1838 type CanEnd = Input::CanEnd;
1839
1840 #[inline(always)]
1841 fn pull(
1842 self: ::std::pin::Pin<&mut Self>,
1843 ctx: &mut Self::Ctx<'_>,
1844 ) -> #root::dfir_pipes::pull::PullStep<Self::Item, Self::Meta, Self::CanPend, Self::CanEnd> {
1845 #root::dfir_pipes::pull::Pull::pull(self.project().inner, ctx)
1846 }
1847
1848 #[inline(always)]
1849 fn size_hint(&self) -> (usize, Option<usize>) {
1850 #root::dfir_pipes::pull::Pull::size_hint(&self.inner)
1851 }
1852 }
1853
1854 Pull {
1855 inner: input
1856 }
1857 }
1858 #work_fn::<_, _>( #ident )
1859 };
1860 }
1861 } else {
1862 quote_spanned! {op_span=>
1863 let #ident = {
1864 #[allow(non_snake_case)]
1865 #[inline(always)]
1866 pub fn #work_fn<Item, Psh>(psh: Psh) -> impl #root::dfir_pipes::push::Push<Item, (), CanPend = Psh::CanPend>
1867 where
1868 Psh: #root::dfir_pipes::push::Push<Item, ()>
1869 {
1870 #root::pin_project_lite::pin_project! {
1871 #[repr(transparent)]
1872 struct PushGuard<Psh> {
1873 #[pin]
1874 inner: Psh,
1875 }
1876 }
1877
1878 impl<Item, Psh> #root::dfir_pipes::push::Push<Item, ()> for PushGuard<Psh>
1879 where
1880 Psh: #root::dfir_pipes::push::Push<Item, ()>,
1881 {
1882 type Ctx<'ctx> = Psh::Ctx<'ctx>;
1883
1884 type CanPend = Psh::CanPend;
1885
1886 #[inline(always)]
1887 fn poll_ready(
1888 self: ::std::pin::Pin<&mut Self>,
1889 ctx: &mut Self::Ctx<'_>,
1890 ) -> #root::dfir_pipes::push::PushStep<Self::CanPend> {
1891 #root::dfir_pipes::push::Push::poll_ready(self.project().inner, ctx)
1892 }
1893
1894 #[inline(always)]
1895 fn start_send(
1896 self: ::std::pin::Pin<&mut Self>,
1897 item: Item,
1898 meta: (),
1899 ) {
1900 #root::dfir_pipes::push::Push::start_send(self.project().inner, item, meta)
1901 }
1902
1903 #[inline(always)]
1904 fn poll_finalize(
1905 self: ::std::pin::Pin<&mut Self>,
1906 ctx: &mut Self::Ctx<'_>,
1907 ) -> #root::dfir_pipes::push::PushStep<Self::CanPend> {
1908 #root::dfir_pipes::push::Push::poll_finalize(self.project().inner, ctx)
1909 }
1910
1911 #[inline(always)]
1912 fn size_hint(
1913 self: ::std::pin::Pin<&mut Self>,
1914 hint: (usize, Option<usize>),
1915 ) {
1916 #root::dfir_pipes::push::Push::size_hint(self.project().inner, hint)
1917 }
1918 }
1919
1920 PushGuard {
1921 inner: psh
1922 }
1923 }
1924 #work_fn( #ident )
1925 };
1926 }
1927 };
1928 subgraph_op_iter_code.push(type_guard);
1929 }
1930 subgraph_op_iter_after_code.push(write_iterator_after);
1931 }
1932 }
1933
1934 {
1935 let pull_ident = if 0 < pull_to_push_idx {
1937 self.node_as_ident(subgraph_nodes[pull_to_push_idx - 1], false)
1938 } else {
1939 recv_port_idents[0].clone()
1941 };
1942
1943 #[rustfmt::skip]
1944 let push_ident = if let Some(&node_id) =
1945 subgraph_nodes.get(pull_to_push_idx)
1946 {
1947 self.node_as_ident(node_id, false)
1948 } else if 1 == send_port_idents.len() {
1949 send_port_idents[0].clone()
1951 } else {
1952 diagnostics.push(Diagnostic::spanned(
1953 pull_ident.span(),
1954 Level::Error,
1955 "Degenerate subgraph detected, is there a disconnected `null()` or other degenerate pipeline somewhere?",
1956 ));
1957 continue;
1958 };
1959
1960 let pivot_span = pull_ident
1962 .span()
1963 .join(push_ident.span())
1964 .unwrap_or_else(|| push_ident.span());
1965 let root = change_spans(root.clone(), pivot_span);
1966 if options.exclude_type_guards {
1967 subgraph_op_iter_code.push(quote_spanned! {pivot_span=>
1970 #root::dfir_pipes::pull::Pull::send_push(#pull_ident, #push_ident).await;
1971 });
1972 } else {
1973 let pivot_fn_ident = Ident::new(
1974 &format!("pivot_run_sg_{:?}", subgraph_id.data()),
1975 pivot_span,
1976 );
1977 subgraph_op_iter_code.push(quote_spanned! {pivot_span=>
1978 #[inline(always)]
1979 fn #pivot_fn_ident<Pul, Psh, Item>(pull: Pul, push: Psh)
1980 -> impl ::std::future::Future<Output = ()>
1981 where
1982 Pul: #root::dfir_pipes::pull::Pull<Item = Item>,
1983 Psh: #root::dfir_pipes::push::Push<Item, Pul::Meta>,
1984 {
1985 #root::dfir_pipes::pull::Pull::send_push(pull, push)
1986 }
1987 (#pivot_fn_ident)(#pull_ident, #push_ident).await;
1988 });
1989 }
1990 }
1991 };
1992
1993 let sg_fut_ident = subgraph_id.as_ident(Span::call_site());
1997
1998 let send_metrics_code = if options.include_metrics_tracking {
2000 send_hoffs
2001 .iter()
2002 .zip(send_buf_idents.iter())
2003 .zip(send_kinds.iter())
2004 .map(|((&hoff_id, buf_ident), &kind)| {
2005 let hoff_ffi = hoff_id.data().as_ffi();
2006 let len_expr = match kind {
2007 HandoffKind::Singleton | HandoffKind::Optional => {
2008 quote! { if #buf_ident.is_some() { 1 } else { 0 } }
2009 }
2010 HandoffKind::Vec => {
2011 quote! { #buf_ident.len() }
2012 }
2013 };
2014 quote! {
2015 __dfir_metrics.handoffs[
2016 #root::slotmap::KeyData::from_ffi(#hoff_ffi).into()
2017 ].curr_items_count.set(#len_expr);
2018 }
2019 })
2020 .collect::<Vec<_>>()
2021 } else {
2022 Vec::new()
2023 };
2024
2025 let send_hoff_make_code = send_buf_idents.iter()
2029 .zip(send_kinds.iter())
2030 .zip(send_hoffs.iter())
2031 .filter_map(|((buf_ident, &kind), &hoff_id)| {
2032 let span = buf_ident.span();
2033 if back_edge_hoffs_and_lazyness.contains_key(hoff_id) {
2034 Some(quote_spanned! {span=>
2037 #buf_ident.clear();
2038 })
2039 } else {
2040 let receiver_loop = self
2043 .node_successors(hoff_id)
2044 .next()
2045 .and_then(|(_, succ)| self.node_loop(succ));
2046 let is_exit = if let Some(sender_loop) = sg_loop {
2047 receiver_loop == self.loop_parent(sender_loop)
2048 } else {
2049 false
2050 };
2051 if is_exit {
2052 None
2054 } else {
2055 Some(match kind {
2056 HandoffKind::Vec => quote_spanned! {span=>
2057 let mut #buf_ident = #root::bumpalo::collections::Vec::new_in(&#bump_ident);
2058 },
2059 HandoffKind::Singleton | HandoffKind::Optional => quote_spanned! {span=>
2060 let mut #buf_ident = ::std::option::Option::None;
2061 },
2062 })
2063 }
2064 }
2065 })
2066 .collect::<Vec<_>>();
2067 let recv_hoff_drop_code = recv_buf_idents
2071 .iter()
2072 .zip(recv_hoffs.iter())
2073 .filter(|&(_, &hoff_id)| !back_edge_hoffs_and_lazyness.contains_key(hoff_id))
2074 .map(|(buf_ident, _)| {
2075 let span = buf_ident.span();
2076 quote_spanned! {span=>
2077 let _ = #buf_ident;
2078 }
2079 });
2080
2081 let run_sg = if options.include_metrics_tracking {
2082 quote! {
2083 let sg_metrics = &__dfir_metrics.subgraphs[
2085 #root::slotmap::KeyData::from_ffi(#sg_metrics_ffi).into()
2086 ];
2087 #root::scheduled::metrics::InstrumentSubgraph::new(
2088 #sg_fut_ident, sg_metrics
2089 ).await;
2090 sg_metrics.total_run_count.update(|x| x + 1);
2091
2092 #( #send_metrics_code )*
2094 }
2095 } else {
2096 quote! {
2097 #sg_fut_ident.await;
2098 }
2099 };
2100
2101 let sg_block = quote! {
2103 #( #send_hoff_make_code )*
2105
2106 let #sg_fut_ident = async {
2107 let #context = &#df;
2108 #( #recv_port_code )*
2109 #( #send_port_code )*
2110 #( #subgraph_op_iter_code )*
2111 #( #subgraph_op_iter_after_code )*
2112 };
2113 {
2114 #run_sg
2115
2116 #( #recv_hoff_drop_code )*
2118 }
2119 };
2120 if let Some((_, body)) = loop_stack.last_mut() {
2121 body.extend(sg_block);
2122 } else {
2123 current_output.extend(sg_block);
2124 }
2125 }
2126 }
2127
2128 let gated_subgraph_code = {
2130 while let Some((closed_loop, child_body)) = loop_stack.pop() {
2131 let target = if let Some((_, parent_body)) = loop_stack.last_mut() {
2132 parent_body
2133 } else {
2134 &mut current_output
2135 };
2136 self.emit_loop_gate(
2137 closed_loop,
2138 child_body,
2139 &loop_input_handoffs,
2140 &back_edge_hoffs_and_lazyness,
2141 &loop_swap_code,
2142 target,
2143 );
2144 }
2145 current_output
2146 };
2147
2148 if diagnostics.has_error() {
2149 return Err(std::mem::take(diagnostics));
2150 }
2151 let _ = diagnostics; let (meta_graph_arg, diagnostics_arg) = if !options.exclude_meta {
2154 let meta_graph_json = serde_json::to_string(&self).unwrap();
2155 let meta_graph_json = Literal::string(&meta_graph_json);
2156
2157 let serde_diagnostics: Vec<_> = diagnostics.iter().map(Diagnostic::to_serde).collect();
2158 let diagnostics_json = serde_json::to_string(&*serde_diagnostics).unwrap();
2159 let diagnostics_json = Literal::string(&diagnostics_json);
2160
2161 (
2162 quote! { Some(#meta_graph_json) },
2163 quote! { Some(#diagnostics_json) },
2164 )
2165 } else {
2166 (quote! { None }, quote! { None })
2167 };
2168
2169 let (context_new_code, context_metrics_ty, tick_metrics_code) = if options
2177 .include_metrics_tracking
2178 {
2179 let handoff_inits = handoff_nodes.iter().map(|&(node_id, _, _)| {
2180 let ffi = node_id.data().as_ffi();
2181 quote! {
2182 dfir_metrics.handoffs.insert(
2183 #root::slotmap::KeyData::from_ffi(#ffi).into(),
2184 ::std::default::Default::default(),
2185 );
2186 }
2187 });
2188 let subgraph_inits = all_subgraphs.iter().map(|&(sg_id, _)| {
2189 let ffi = sg_id.data().as_ffi();
2190 quote! {
2191 dfir_metrics.subgraphs.insert(
2192 #root::slotmap::KeyData::from_ffi(#ffi).into(),
2193 ::std::default::Default::default(),
2194 );
2195 }
2196 });
2197 let init_code = handoff_inits.chain(subgraph_inits).collect::<Vec<_>>();
2198 (
2199 quote! {
2200 #root::scheduled::context::Context::new_with_metrics({
2201 let mut dfir_metrics = #root::scheduled::metrics::DfirMetrics::default();
2202 #( #init_code )*
2203 dfir_metrics
2204 })
2205 },
2206 quote! {
2207 #root::scheduled::metrics::DfirMetrics
2208 },
2209 quote! {
2210 let __dfir_metrics = #df.metrics();
2211 },
2212 )
2213 } else {
2214 (
2215 quote! {
2216 #root::scheduled::context::Context::new()
2217 },
2218 quote! { () },
2219 quote! {},
2220 )
2221 };
2222
2223 let back_buffer_idents = back_buffer_idents_laziness
2225 .iter()
2226 .map(|(back_ident, _, _)| back_ident);
2227 let defer_tick_buf_idents = back_buffer_idents_laziness
2229 .iter()
2230 .map(|(_, buf_ident, _)| buf_ident);
2231 let non_lazy_schedule_idents: Vec<&Ident> = handoff_nodes
2236 .iter()
2237 .filter_map(|&(hoff_id, _, _)| {
2238 let delay_type = self.handoff_delay_type(hoff_id)?;
2239 if matches!(delay_type, DelayType::TickLazy | DelayType::LoopLazy) {
2241 return None;
2242 }
2243 let span = self.nodes[hoff_id].span();
2244 let expected_back_ident = self.hoff_back_ident(hoff_id, span);
2245 let entry = back_buffer_idents_laziness
2246 .iter()
2247 .find(|(back_ident, _, _)| *back_ident == expected_back_ident)?;
2248
2249 if delay_type == DelayType::Tick {
2251 let consumer_loop = self
2252 .node_successors(hoff_id)
2253 .next()
2254 .and_then(|(_, succ)| self.node_subgraph(succ))
2255 .and_then(|sg| self.subgraph_loop(sg));
2256 if consumer_loop.is_some_and(|lid| self.loop_parent(lid).is_none()) {
2257 return Some(&entry.0); }
2259 }
2260 Some(&entry.1) })
2262 .collect();
2263
2264 Ok(quote! {
2267 {
2268 #prefix
2269
2270 use #root::{var_expr, var_args};
2271
2272 #[allow(unused_mut)]
2273 let mut #df = #context_new_code;
2274
2275 #( #op_prologue_code )*
2276
2277 #( let mut #back_buffer_idents = ::std::vec::Vec::new(); )*
2281 #( let mut #defer_tick_buf_idents = ::std::vec::Vec::new(); )*
2282
2283 let mut #bump_ident = #root::bumpalo::Bump::new();
2285
2286 let mut __dfir_work_done = true;
2291 #[allow(unused_qualifications, unused_mut, unused_variables, clippy::await_holding_refcell_ref, clippy::deref_addrof)]
2292 let __dfir_inline_tick = async move |#df: &mut #root::scheduled::context::Context<#context_metrics_ty>| {
2293 #bump_ident.reset();
2295
2296 {
2297 #tick_metrics_code
2298
2299 #gated_subgraph_code
2300
2301 #[allow(clippy::nonminimal_bool, reason = "codegen")]
2304 if false #( || !#non_lazy_schedule_idents.is_empty() )* {
2305 #df.schedule_subgraph(true);
2306 }
2307
2308 #( #back_edge_swap_code )*
2311 }
2312
2313 #( #op_tick_end_code )*
2315
2316 #df.__end_tick();
2317
2318 ::std::mem::take(&mut __dfir_work_done)
2319 };
2320 #root::scheduled::context::Dfir::new(
2321 #root::scheduled::context::TickClosureFn::new(__dfir_inline_tick),
2322 #df,
2323 #meta_graph_arg,
2324 #diagnostics_arg,
2325 )
2326 }
2327 })
2328 }
2329
2330 pub fn node_color_map(&self) -> SparseSecondaryMap<GraphNodeId, Color> {
2333 let mut node_color_map: SparseSecondaryMap<GraphNodeId, Color> = self
2334 .node_ids()
2335 .filter_map(|node_id| {
2336 let op_color = self.node_color(node_id)?;
2337 Some((node_id, op_color))
2338 })
2339 .collect();
2340
2341 for sg_nodes in self.subgraph_nodes.values() {
2343 let pull_to_push_idx = self.find_pull_to_push_idx(sg_nodes);
2344
2345 for (idx, node_id) in sg_nodes.iter().copied().enumerate() {
2346 let is_pull = idx < pull_to_push_idx;
2347 node_color_map.insert(node_id, if is_pull { Color::Pull } else { Color::Push });
2348 }
2349 }
2350
2351 node_color_map
2352 }
2353
2354 pub fn to_mermaid(&self, write_config: &WriteConfig) -> String {
2356 let mut output = String::new();
2357 self.write_mermaid(&mut output, write_config).unwrap();
2358 output
2359 }
2360
2361 pub fn write_mermaid(
2363 &self,
2364 output: impl std::fmt::Write,
2365 write_config: &WriteConfig,
2366 ) -> std::fmt::Result {
2367 let mut graph_write = Mermaid::new(output);
2368 self.write_graph(&mut graph_write, write_config)
2369 }
2370
2371 pub fn to_dot(&self, write_config: &WriteConfig) -> String {
2373 let mut output = String::new();
2374 let mut graph_write = Dot::new(&mut output);
2375 self.write_graph(&mut graph_write, write_config).unwrap();
2376 output
2377 }
2378
2379 pub fn write_dot(
2381 &self,
2382 output: impl std::fmt::Write,
2383 write_config: &WriteConfig,
2384 ) -> std::fmt::Result {
2385 let mut graph_write = Dot::new(output);
2386 self.write_graph(&mut graph_write, write_config)
2387 }
2388
2389 pub(crate) fn write_graph<W>(
2391 &self,
2392 mut graph_write: W,
2393 write_config: &WriteConfig,
2394 ) -> Result<(), W::Err>
2395 where
2396 W: GraphWrite,
2397 {
2398 fn helper_edge_label(
2399 src_port: &PortIndexValue,
2400 dst_port: &PortIndexValue,
2401 ) -> Option<String> {
2402 let src_label = match src_port {
2403 PortIndexValue::Path(path) => Some(path.to_token_stream().to_string()),
2404 PortIndexValue::Int(index) => Some(index.value.to_string()),
2405 _ => None,
2406 };
2407 let dst_label = match dst_port {
2408 PortIndexValue::Path(path) => Some(path.to_token_stream().to_string()),
2409 PortIndexValue::Int(index) => Some(index.value.to_string()),
2410 _ => None,
2411 };
2412 let label = match (src_label, dst_label) {
2413 (Some(l1), Some(l2)) => Some(format!("{}\n{}", l1, l2)),
2414 (Some(l1), None) => Some(l1),
2415 (None, Some(l2)) => Some(l2),
2416 (None, None) => None,
2417 };
2418 label
2419 }
2420
2421 let node_color_map = self.node_color_map();
2423
2424 graph_write.write_prologue()?;
2426
2427 let mut skipped_handoffs = BTreeSet::new();
2429 for (node_id, node) in self.nodes() {
2430 if matches!(node, GraphNode::Handoff { .. }) && write_config.no_handoffs {
2431 skipped_handoffs.insert(node_id);
2432 continue;
2433 }
2434 graph_write.write_node_definition(
2435 node_id,
2436 &if write_config.op_short_text {
2437 node.to_name_string()
2438 } else if write_config.op_text_no_imports {
2439 let full_text = node.to_pretty_string();
2441 let mut output = String::new();
2442 for sentence in full_text.split('\n') {
2443 if sentence.trim().starts_with("use") {
2444 continue;
2445 }
2446 output.push('\n');
2447 output.push_str(sentence);
2448 }
2449 output.into()
2450 } else {
2451 node.to_pretty_string()
2452 },
2453 if write_config.no_pull_push {
2454 None
2455 } else {
2456 node_color_map.get(node_id).copied()
2457 },
2458 )?;
2459 }
2460
2461 for (edge_id, (src_id, mut dst_id)) in self.edges() {
2463 if skipped_handoffs.contains(&src_id) {
2465 continue;
2466 }
2467
2468 let (src_port, mut dst_port) = self.edge_ports(edge_id);
2469 if skipped_handoffs.contains(&dst_id) {
2470 let mut handoff_succs = self.node_successors(dst_id);
2474 if handoff_succs.len() == 0 {
2475 continue;
2476 }
2477 let (succ_edge, succ_node) = handoff_succs.next().unwrap();
2478 dst_id = succ_node;
2479 dst_port = self.edge_ports(succ_edge).1;
2480 }
2481
2482 let label = helper_edge_label(src_port, dst_port);
2483 let delay_type = self
2484 .node_op_inst(dst_id)
2485 .and_then(|op_inst| (op_inst.op_constraints.input_delaytype_fn)(dst_port));
2486 graph_write.write_edge(src_id, dst_id, delay_type, label.as_deref(), false)?;
2487 }
2488
2489 if !write_config.no_references {
2491 for dst_id in self.node_ids() {
2492 for src_ref_id in self
2493 .node_handoff_references(dst_id)
2494 .iter()
2495 .filter_map(|r| r.node_id)
2496 {
2497 let resolved_src = if skipped_handoffs.contains(&src_ref_id) {
2500 self.node_predecessor_nodes(src_ref_id).next()
2501 } else {
2502 Some(src_ref_id)
2503 };
2504 let Some(resolved_src) = resolved_src else {
2505 continue;
2506 };
2507 let label = None;
2508 graph_write.write_edge(resolved_src, dst_id, None, label, true)?;
2509 }
2510 }
2511 }
2512
2513 let loop_subgraphs = self.subgraph_ids().map(|sg_id| {
2521 let loop_id = if write_config.no_loops {
2522 None
2523 } else {
2524 self.subgraph_loop(sg_id)
2525 };
2526 (loop_id, sg_id)
2527 });
2528 let loop_subgraphs = into_group_map(loop_subgraphs);
2529 for (loop_id, subgraph_ids) in loop_subgraphs {
2530 if let Some(loop_id) = loop_id {
2531 graph_write.write_loop_start(loop_id)?;
2532 }
2533
2534 let subgraph_varnames_nodes = subgraph_ids.into_iter().flat_map(|sg_id| {
2536 self.subgraph(sg_id).iter().copied().map(move |node_id| {
2537 let opt_sg_id = if write_config.no_subgraphs {
2538 None
2539 } else {
2540 Some(sg_id)
2541 };
2542 (opt_sg_id, (self.node_varname(node_id), node_id))
2543 })
2544 });
2545 let subgraph_varnames_nodes = into_group_map(subgraph_varnames_nodes);
2546 for (sg_id, varnames) in subgraph_varnames_nodes {
2547 if let Some(sg_id) = sg_id {
2548 graph_write.write_subgraph_start(sg_id)?;
2549 }
2550
2551 let varname_nodes = varnames.into_iter().map(|(varname, node)| {
2553 let varname = if write_config.no_varnames {
2554 None
2555 } else {
2556 varname
2557 };
2558 (varname, node)
2559 });
2560 let varname_nodes = into_group_map(varname_nodes);
2561 for (varname, node_ids) in varname_nodes {
2562 if let Some(varname) = varname {
2563 graph_write.write_varname_start(&varname.0.to_string(), sg_id)?;
2564 }
2565
2566 for node_id in node_ids {
2568 graph_write.write_node(node_id)?;
2569 }
2570
2571 if varname.is_some() {
2572 graph_write.write_varname_end()?;
2573 }
2574 }
2575
2576 if sg_id.is_some() {
2577 graph_write.write_subgraph_end()?;
2578 }
2579 }
2580
2581 if loop_id.is_some() {
2582 graph_write.write_loop_end()?;
2583 }
2584 }
2585
2586 graph_write.write_epilogue()?;
2588
2589 Ok(())
2590 }
2591
2592 pub fn surface_syntax_string(&self) -> String {
2594 let mut string = String::new();
2595 self.write_surface_syntax(&mut string).unwrap();
2596 string
2597 }
2598
2599 pub fn write_surface_syntax(&self, write: &mut impl std::fmt::Write) -> std::fmt::Result {
2601 for (key, node) in self.nodes.iter() {
2602 match node {
2603 GraphNode::Operator(op) => {
2604 writeln!(write, "_{:?} = {};", key.data(), op.to_token_stream())?;
2605 }
2606 GraphNode::Handoff {
2607 kind: HandoffKind::Vec,
2608 ..
2609 } => {
2610 writeln!(write, "_{:?} = handoff();", key.data())?;
2611 }
2612 GraphNode::Handoff {
2613 kind: HandoffKind::Singleton,
2614 ..
2615 } => {
2616 writeln!(write, "_{:?} = singleton();", key.data())?;
2617 }
2618 GraphNode::Handoff {
2619 kind: HandoffKind::Optional,
2620 ..
2621 } => {
2622 writeln!(write, "_{:?} = optional();", key.data())?;
2623 }
2624 GraphNode::ModuleBoundary { .. } => panic!(),
2625 }
2626 }
2627 writeln!(write)?;
2628 for (e, (src_key, dst_key)) in self.graph.edges() {
2629 let (src_port, dst_port) = self.edge_ports(e);
2630 let src_port_str = if src_port.is_specified() {
2631 format!("[{}]", src_port)
2632 } else {
2633 String::new()
2634 };
2635 let dst_port_str = if dst_port.is_specified() {
2636 format!("[{}]", dst_port)
2637 } else {
2638 String::new()
2639 };
2640 writeln!(
2641 write,
2642 "_{:?}{} -> {}_{:?};",
2643 src_key.data(),
2644 src_port_str,
2645 dst_port_str,
2646 dst_key.data()
2647 )?;
2648 }
2649 Ok(())
2650 }
2651
2652 pub fn mermaid_string_flat(&self) -> String {
2654 let mut string = String::new();
2655 self.write_mermaid_flat(&mut string).unwrap();
2656 string
2657 }
2658
2659 pub fn write_mermaid_flat(&self, write: &mut impl std::fmt::Write) -> std::fmt::Result {
2661 writeln!(write, "flowchart TB")?;
2662 for (key, node) in self.nodes.iter() {
2663 match node {
2664 GraphNode::Operator(operator) => writeln!(
2665 write,
2666 " %% {span}\n {id:?}[\"{row_col} <tt>{code}</tt>\"]",
2667 span = PrettySpan(node.span()),
2668 id = key.data(),
2669 row_col = PrettyRowCol(node.span()),
2670 code = operator
2671 .to_token_stream()
2672 .to_string()
2673 .replace('&', "&")
2674 .replace('<', "<")
2675 .replace('>', ">")
2676 .replace('"', """)
2677 .replace('\n', "<br>"),
2678 ),
2679 GraphNode::Handoff {
2680 kind: HandoffKind::Vec,
2681 ..
2682 } => {
2683 writeln!(write, r#" {:?}{{"{}"}}"#, key.data(), HANDOFF_NODE_STR)
2684 }
2685 GraphNode::Handoff {
2686 kind: HandoffKind::Singleton | HandoffKind::Optional,
2687 ..
2688 } => {
2689 writeln!(
2690 write,
2691 r#" {:?}{{"{}"}}"#,
2692 key.data(),
2693 SINGLETON_SLOT_NODE_STR
2694 )
2695 }
2696 GraphNode::ModuleBoundary { .. } => {
2697 writeln!(
2698 write,
2699 r#" {:?}{{"{}"}}"#,
2700 key.data(),
2701 MODULE_BOUNDARY_NODE_STR
2702 )
2703 }
2704 }?;
2705 }
2706 writeln!(write)?;
2707 for (_e, (src_key, dst_key)) in self.graph.edges() {
2708 writeln!(write, " {:?}-->{:?}", src_key.data(), dst_key.data())?;
2709 }
2710 Ok(())
2711 }
2712}
2713
2714impl DfirGraph {
2716 pub fn loop_ids(&self) -> slotmap::basic::Keys<'_, GraphLoopId, Vec<GraphNodeId>> {
2718 self.loop_nodes.keys()
2719 }
2720
2721 pub fn loops(&self) -> slotmap::basic::Iter<'_, GraphLoopId, Vec<GraphNodeId>> {
2723 self.loop_nodes.iter()
2724 }
2725
2726 pub fn loop_nodes(&self, loop_id: GraphLoopId) -> &[GraphNodeId] {
2728 self.loop_nodes.get(loop_id).unwrap()
2729 }
2730
2731 pub fn insert_loop(&mut self, parent_loop: Option<GraphLoopId>) -> GraphLoopId {
2733 let loop_id = self.loop_nodes.insert(Vec::new());
2734 self.loop_children.insert(loop_id, Vec::new());
2735 if let Some(parent_loop) = parent_loop {
2736 self.loop_parent.insert(loop_id, parent_loop);
2737 self.loop_children
2738 .get_mut(parent_loop)
2739 .unwrap()
2740 .push(loop_id);
2741 } else {
2742 self.root_loops.push(loop_id);
2743 }
2744 loop_id
2745 }
2746
2747 pub fn node_loop(&self, node_id: GraphNodeId) -> Option<GraphLoopId> {
2749 self.node_loops.get(node_id).copied()
2750 }
2751
2752 pub fn subgraph_loop(&self, subgraph_id: GraphSubgraphId) -> Option<GraphLoopId> {
2754 let &node_id = self.subgraph(subgraph_id).first().unwrap();
2755 let out = self.node_loop(node_id);
2756 debug_assert!(
2757 self.subgraph(subgraph_id)
2758 .iter()
2759 .all(|&node_id| self.node_loop(node_id) == out),
2760 "Subgraph nodes should all have the same loop context."
2761 );
2762 out
2763 }
2764
2765 pub fn loop_parent(&self, loop_id: GraphLoopId) -> Option<GraphLoopId> {
2767 self.loop_parent.get(loop_id).copied()
2768 }
2769
2770 pub fn loop_children(&self, loop_id: GraphLoopId) -> &Vec<GraphLoopId> {
2772 self.loop_children.get(loop_id).unwrap()
2773 }
2774
2775 pub fn root_loops(&self) -> &[GraphLoopId] {
2777 &self.root_loops
2778 }
2779}
2780
2781#[derive(Clone, Debug, Default)]
2783#[non_exhaustive]
2784pub struct AsCodeOptions {
2785 pub exclude_type_guards: bool,
2804 pub require_flame_attribution: bool,
2813 pub exclude_meta: bool,
2816 pub include_metrics_tracking: bool,
2824}
2825
2826#[derive(Clone, Debug, Default)]
2828#[cfg_attr(feature = "clap-derive", derive(clap::Args))]
2829pub struct WriteConfig {
2830 #[cfg_attr(feature = "clap-derive", arg(long))]
2832 pub no_subgraphs: bool,
2833 #[cfg_attr(feature = "clap-derive", arg(long))]
2835 pub no_varnames: bool,
2836 #[cfg_attr(feature = "clap-derive", arg(long))]
2838 pub no_pull_push: bool,
2839 #[cfg_attr(feature = "clap-derive", arg(long))]
2841 pub no_handoffs: bool,
2842 #[cfg_attr(feature = "clap-derive", arg(long))]
2844 pub no_references: bool,
2845 #[cfg_attr(feature = "clap-derive", arg(long))]
2847 pub no_loops: bool,
2848
2849 #[cfg_attr(feature = "clap-derive", arg(long))]
2851 pub op_short_text: bool,
2852 #[cfg_attr(feature = "clap-derive", arg(long))]
2854 pub op_text_no_imports: bool,
2855}
2856
2857#[derive(Copy, Clone, Debug)]
2859#[cfg_attr(feature = "clap-derive", derive(clap::Parser, clap::ValueEnum))]
2860pub enum WriteGraphType {
2861 Mermaid,
2863 Dot,
2865}
2866
2867fn tokens_contain_ident(tokens: TokenStream, ident: &Ident) -> bool {
2869 tokens.into_iter().any(|tt| match tt {
2870 TokenTree::Ident(i) => i == *ident,
2871 TokenTree::Group(group) => tokens_contain_ident(group.stream(), ident),
2872 TokenTree::Punct(_) | TokenTree::Literal(_) => false,
2873 })
2874}
2875
2876fn into_group_map<K, V>(iter: impl IntoIterator<Item = (K, V)>) -> BTreeMap<K, Vec<V>>
2878where
2879 K: Ord,
2880{
2881 let mut out: BTreeMap<_, Vec<_>> = BTreeMap::new();
2882 for (k, v) in iter {
2883 out.entry(k).or_default().push(v);
2884 }
2885 out
2886}