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};
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> {
602 let mod_bound_nodes = self
603 .nodes()
604 .filter(|(_nid, node)| matches!(node, GraphNode::ModuleBoundary { .. }))
605 .map(|(nid, _node)| nid)
606 .collect::<Vec<_>>();
607
608 for mod_bound_node in mod_bound_nodes {
609 self.remove_module_boundary(mod_bound_node)?;
610 }
611
612 Ok(())
613 }
614
615 fn remove_module_boundary(&mut self, mod_bound_node: GraphNodeId) -> Result<(), Diagnostic> {
619 assert!(
620 self.node_subgraph.is_empty() && self.subgraph_nodes.is_empty(),
621 "Should not remove intermediate node after subgraph partitioning"
622 );
623
624 let mut mod_pred_ports = BTreeMap::new();
625 let mut mod_succ_ports = BTreeMap::new();
626
627 for mod_out_edge in self.node_predecessor_edges(mod_bound_node) {
628 let (pred_port, succ_port) = self.edge_ports(mod_out_edge);
629 mod_pred_ports.insert(succ_port.clone(), (mod_out_edge, pred_port.clone()));
630 }
631
632 for mod_inn_edge in self.node_successor_edges(mod_bound_node) {
633 let (pred_port, succ_port) = self.edge_ports(mod_inn_edge);
634 mod_succ_ports.insert(pred_port.clone(), (mod_inn_edge, succ_port.clone()));
635 }
636
637 if mod_pred_ports.keys().collect::<BTreeSet<_>>()
638 != mod_succ_ports.keys().collect::<BTreeSet<_>>()
639 {
640 let GraphNode::ModuleBoundary { input, import_expr } = self.node(mod_bound_node) else {
642 panic!();
643 };
644
645 if *input {
646 return Err(Diagnostic {
647 span: *import_expr,
648 level: Level::Error,
649 message: format!(
650 "The ports into the module did not match. input: {:?}, expected: {:?}",
651 mod_pred_ports.keys().map(|x| x.to_string()).join(", "),
652 mod_succ_ports.keys().map(|x| x.to_string()).join(", ")
653 ),
654 });
655 } else {
656 return Err(Diagnostic {
657 span: *import_expr,
658 level: Level::Error,
659 message: format!(
660 "The ports out of the module did not match. output: {:?}, expected: {:?}",
661 mod_succ_ports.keys().map(|x| x.to_string()).join(", "),
662 mod_pred_ports.keys().map(|x| x.to_string()).join(", "),
663 ),
664 });
665 }
666 }
667
668 for (port, (pred_edge, pred_port)) in mod_pred_ports {
669 let (succ_edge, succ_port) = mod_succ_ports.remove(&port).unwrap();
670
671 let (src, _) = self.edge(pred_edge);
672 let (_, dst) = self.edge(succ_edge);
673 self.remove_edge(pred_edge);
674 self.remove_edge(succ_edge);
675
676 let new_edge_id = self.graph.insert_edge(src, dst);
677 self.ports.insert(new_edge_id, (pred_port, succ_port));
678 }
679
680 self.graph.remove_vertex(mod_bound_node);
681 self.nodes.remove(mod_bound_node);
682
683 Ok(())
684 }
685}
686
687impl DfirGraph {
689 pub fn edge(&self, edge_id: GraphEdgeId) -> (GraphNodeId, GraphNodeId) {
691 let (src, dst) = self.graph.edge(edge_id).expect("Edge not found.");
692 (src, dst)
693 }
694
695 pub fn edge_ports(&self, edge_id: GraphEdgeId) -> (&PortIndexValue, &PortIndexValue) {
697 let (src_port, dst_port) = self.ports.get(edge_id).expect("Edge not found.");
698 (src_port, dst_port)
699 }
700
701 pub fn edge_ids(&self) -> slotmap::basic::Keys<'_, GraphEdgeId, (GraphNodeId, GraphNodeId)> {
703 self.graph.edge_ids()
704 }
705
706 pub fn edges(
708 &self,
709 ) -> impl '_
710 + ExactSizeIterator<Item = (GraphEdgeId, (GraphNodeId, GraphNodeId))>
711 + FusedIterator
712 + Clone
713 + Debug {
714 self.graph.edges()
715 }
716
717 pub fn insert_edge(
719 &mut self,
720 src: GraphNodeId,
721 src_port: PortIndexValue,
722 dst: GraphNodeId,
723 dst_port: PortIndexValue,
724 ) -> GraphEdgeId {
725 let edge_id = self.graph.insert_edge(src, dst);
726 self.ports.insert(edge_id, (src_port, dst_port));
727 edge_id
728 }
729
730 pub fn remove_edge(&mut self, edge: GraphEdgeId) {
732 let (_src, _dst) = self.graph.remove_edge(edge).unwrap();
733 let (_src_port, _dst_port) = self.ports.remove(edge).unwrap();
734 }
735}
736
737impl DfirGraph {
739 pub fn subgraph(&self, subgraph_id: GraphSubgraphId) -> &Vec<GraphNodeId> {
741 self.subgraph_nodes
742 .get(subgraph_id)
743 .expect("Subgraph not found.")
744 }
745
746 pub fn subgraph_ids(&self) -> slotmap::basic::Keys<'_, GraphSubgraphId, Vec<GraphNodeId>> {
748 self.subgraph_nodes.keys()
749 }
750
751 pub fn subgraph_toposort(&self) -> &[GraphSubgraphId] {
753 &self.subgraph_toposort
754 }
755
756 pub fn set_subgraph_toposort(&mut self, order: Vec<GraphSubgraphId>) {
758 self.subgraph_toposort = order;
759 }
760
761 pub fn subgraphs(&self) -> slotmap::basic::Iter<'_, GraphSubgraphId, Vec<GraphNodeId>> {
763 self.subgraph_nodes.iter()
764 }
765
766 pub fn insert_subgraph(
768 &mut self,
769 node_ids: Vec<GraphNodeId>,
770 ) -> Result<GraphSubgraphId, (GraphNodeId, GraphSubgraphId)> {
771 for &node_id in node_ids.iter() {
773 if let Some(&old_sg_id) = self.node_subgraph.get(node_id) {
774 return Err((node_id, old_sg_id));
775 }
776 }
777 let subgraph_id = self.subgraph_nodes.insert_with_key(|sg_id| {
778 for &node_id in node_ids.iter() {
779 self.node_subgraph.insert(node_id, sg_id);
780 }
781 node_ids
782 });
783
784 Ok(subgraph_id)
785 }
786
787 pub fn remove_from_subgraph(&mut self, node_id: GraphNodeId) -> bool {
789 if let Some(old_sg_id) = self.node_subgraph.remove(node_id) {
790 self.subgraph_nodes[old_sg_id].retain(|&other_node_id| other_node_id != node_id);
791 true
792 } else {
793 false
794 }
795 }
796
797 pub fn handoff_delay_type(&self, node_id: GraphNodeId) -> Option<DelayType> {
799 self.handoff_delay_type.get(node_id).copied()
800 }
801
802 pub fn set_handoff_delay_type(&mut self, node_id: GraphNodeId, delay_type: DelayType) {
804 self.handoff_delay_type.insert(node_id, delay_type);
805 }
806
807 fn find_pull_to_push_idx(&self, subgraph_nodes: &[GraphNodeId]) -> usize {
809 subgraph_nodes
810 .iter()
811 .position(|&node_id| {
812 self.node_color(node_id)
813 .is_some_and(|color| Color::Pull != color)
814 })
815 .unwrap_or(subgraph_nodes.len())
816 }
817}
818
819impl DfirGraph {
821 fn node_as_ident(&self, node_id: GraphNodeId, is_pred: bool) -> Ident {
823 let name = match &self.nodes[node_id] {
824 GraphNode::Operator(_) => format!("op_{:?}", node_id.data()),
825 GraphNode::Handoff {
826 kind: HandoffKind::Vec,
827 ..
828 } => format!(
829 "hoff_{:?}_{}",
830 node_id.data(),
831 if is_pred { "recv" } else { "send" }
832 ),
833 GraphNode::Handoff {
834 kind: HandoffKind::Singleton | HandoffKind::Optional,
835 ..
836 } => format!(
837 "singleton_{:?}_{}",
838 node_id.data(),
839 if is_pred { "recv" } else { "send" }
840 ),
841 GraphNode::ModuleBoundary { .. } => panic!(),
842 };
843 let span = match (is_pred, &self.nodes[node_id]) {
844 (_, GraphNode::Operator(operator)) => operator.span(),
845 (true, &GraphNode::Handoff { src_span, .. }) => src_span,
846 (false, &GraphNode::Handoff { dst_span, .. }) => dst_span,
847 (_, GraphNode::ModuleBoundary { .. }) => panic!(),
848 };
849 Ident::new(&name, span)
850 }
851
852 fn hoff_buf_ident(&self, hoff_id: GraphNodeId, span: Span) -> Ident {
854 Ident::new(&format!("hoff_{:?}_buf", hoff_id.data()), span)
855 }
856
857 fn hoff_back_ident(&self, hoff_id: GraphNodeId, span: Span) -> Ident {
859 Ident::new(&format!("hoff_{:?}_back", hoff_id.data()), span)
860 }
861
862 fn helper_resolve_singletons(&self, node_id: GraphNodeId, span: Span) -> Vec<TokenStream> {
871 self.node_handoff_references(node_id)
872 .iter()
873 .map(|resolved_ref| {
874 let ref_node_id = resolved_ref
876 .node_id
877 .expect("Expected singleton to be resolved but was not, this is a bug.");
878 let is_mut = resolved_ref.is_mut;
879 match self.node(ref_node_id) {
880 GraphNode::Handoff {
881 kind: HandoffKind::Singleton,
882 ..
883 } => {
884 let buf_ident = self.hoff_buf_ident(ref_node_id, span);
885 if is_mut {
886 quote_spanned! {span=> #buf_ident.as_mut().unwrap() }
887 } else {
888 quote_spanned! {span=> #buf_ident.as_ref().unwrap() }
889 }
890 }
891 GraphNode::Handoff {
892 kind: HandoffKind::Optional | HandoffKind::Vec,
893 ..
894 } => {
895 let buf_ident = self.hoff_buf_ident(ref_node_id, span);
896 if is_mut {
897 quote_spanned! {span=> &mut #buf_ident }
898 } else {
899 quote_spanned! {span=> &#buf_ident }
900 }
901 }
902 _ => {
903 unreachable!("Only handoff nodes should be reachable as handoff references")
904 }
905 }
906 })
907 .collect::<Vec<_>>()
908 }
909
910 fn helper_collect_subgraph_handoffs(
913 &self,
914 ) -> SecondaryMap<GraphSubgraphId, (Vec<GraphNodeId>, Vec<GraphNodeId>)> {
915 let mut subgraph_handoffs: SecondaryMap<
917 GraphSubgraphId,
918 (Vec<GraphNodeId>, Vec<GraphNodeId>),
919 > = self
920 .subgraph_nodes
921 .keys()
922 .map(|k| (k, Default::default()))
923 .collect();
924
925 for (hoff_id, hoff) in self.nodes() {
927 if !matches!(hoff, GraphNode::Handoff { .. }) {
928 continue;
929 }
930 for (_edge, succ_id) in self.node_successors(hoff_id) {
932 let succ_sg = self
933 .node_subgraph(succ_id)
934 .expect("bug: successor not in subgraph, may be a doubled/adjacent handoff");
935 subgraph_handoffs[succ_sg].0.push(hoff_id);
936 }
937 for (_edge, pred_id) in self.node_predecessors(hoff_id) {
939 let pred_sg = self
940 .node_subgraph(pred_id)
941 .expect("bug: predecessor not in subgraph, may be a doubled/adjacent handoff");
942 subgraph_handoffs[pred_sg].1.push(hoff_id);
943 }
944 }
945
946 subgraph_handoffs
947 }
948
949 fn helper_loop_output_handoffs(&self) -> SecondaryMap<GraphLoopId, Vec<GraphNodeId>> {
952 let mut loop_hoffs_out = SecondaryMap::<GraphLoopId, Vec<GraphNodeId>>::new();
953
954 for (hoff_id, hoff) in self.nodes() {
955 if !matches!(hoff, GraphNode::Handoff { .. }) {
956 continue;
957 }
958
959 let loop_pred = self
960 .node_predecessors(hoff_id)
961 .next()
962 .and_then(|(_, pred)| self.node_loop(pred));
963 let loop_succ = self
964 .node_successors(hoff_id)
965 .next()
966 .and_then(|(_, succ)| self.node_loop(succ));
967
968 if let Some(loop_pred) = loop_pred
969 && loop_succ == self.loop_parent(loop_pred)
970 {
971 loop_hoffs_out
973 .entry(loop_pred)
974 .expect("loop removed")
975 .or_default()
976 .push(hoff_id);
977 }
978 }
979
980 loop_hoffs_out
981 }
982
983 fn is_inside_loop(&self, node_loop: Option<GraphLoopId>, loop_id: GraphLoopId) -> bool {
985 let mut current = node_loop;
986 while let Some(l) = current {
987 if l == loop_id {
988 return true;
989 }
990 current = self.loop_parent(l);
991 }
992 false
993 }
994
995 fn emit_loop_gate(
1004 &self,
1005 loop_id: GraphLoopId,
1006 child_body: TokenStream,
1007 loop_input_handoffs: &SecondaryMap<GraphLoopId, Vec<GraphNodeId>>,
1008 back_edge_hoffs_and_lazyness: &SparseSecondaryMap<GraphNodeId, bool>,
1009 loop_swap_code: &std::collections::HashMap<GraphLoopId, Vec<TokenStream>>,
1010 output: &mut TokenStream,
1011 ) {
1012 let swap_code = loop_swap_code
1014 .get(&loop_id)
1015 .map(|v| v.as_slice())
1016 .unwrap_or(&[]);
1017
1018 let is_root_loop = self.loop_parent(loop_id).is_none();
1020
1021 let entry_handoffs = loop_input_handoffs.get(loop_id).expect("loop missing");
1023 let mut gate_checks: Vec<TokenStream> = entry_handoffs
1024 .iter()
1025 .filter(|&&hoff_id| {
1026 let is_lazy = self
1029 .node_successors(hoff_id)
1030 .next()
1031 .and_then(|(_, succ)| self.node_op_inst(succ))
1032 .is_some_and(|op_inst| {
1033 op_inst.op_constraints.flo_type == Some(FloType::WindowingLazy)
1034 });
1035 !is_lazy
1036 })
1037 .map(|&hoff_id| {
1038 let span = self.node(hoff_id).span();
1039 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1040 if back_edge_hoffs_and_lazyness.contains_key(hoff_id) {
1041 let back_ident = self.hoff_back_ident(hoff_id, span);
1042 quote_spanned! {span=> !#back_ident.is_empty() }
1043 } else {
1044 quote_spanned! {span=> !#buf_ident.is_empty() }
1045 }
1046 })
1047 .collect();
1048
1049 if !is_root_loop {
1051 for (hoff_id, hoff) in self.nodes() {
1052 if !matches!(hoff, GraphNode::Handoff { .. }) {
1053 continue;
1054 }
1055 let Some(delay_type) = self.handoff_delay_type(hoff_id) else {
1056 continue;
1057 };
1058 if delay_type != DelayType::Loop {
1059 continue;
1060 }
1061 let hoff_loop = self
1063 .node_successors(hoff_id)
1064 .next()
1065 .and_then(|(_, succ)| self.node_subgraph(succ))
1066 .and_then(|sg| self.subgraph_loop(sg));
1067 if hoff_loop != Some(loop_id) {
1068 continue;
1069 }
1070 let span = self.node(hoff_id).span();
1071 let back_ident = self.hoff_back_ident(hoff_id, span);
1072 gate_checks.push(quote_spanned! {span=> !#back_ident.is_empty() });
1073 }
1074 }
1075
1076 if is_root_loop {
1079 for (hoff_id, hoff) in self.nodes() {
1080 if !matches!(hoff, GraphNode::Handoff { .. }) {
1081 continue;
1082 }
1083 let Some(delay_type) = self.handoff_delay_type(hoff_id) else {
1084 continue;
1085 };
1086 if delay_type != DelayType::Tick {
1087 continue;
1088 }
1089 let hoff_loop = self
1091 .node_successors(hoff_id)
1092 .next()
1093 .and_then(|(_, succ)| self.node_subgraph(succ))
1094 .and_then(|sg| self.subgraph_loop(sg));
1095 if hoff_loop != Some(loop_id) {
1096 continue;
1097 }
1098 let span = self.node(hoff_id).span();
1099 let back_ident = self.hoff_back_ident(hoff_id, span);
1100 gate_checks.push(quote_spanned! {span=> !#back_ident.is_empty() });
1101 }
1102 }
1103
1104 let has_eager = entry_handoffs.iter().any(|&hoff_id| {
1109 self.node_successors(hoff_id)
1110 .next()
1111 .and_then(|(_, succ)| self.node_op_inst(succ))
1112 .is_some_and(|op_inst| {
1113 op_inst.op_constraints.flo_type == Some(FloType::WindowingEager)
1114 })
1115 });
1116
1117 if has_eager && is_root_loop {
1118 output.extend(child_body);
1120 output.extend(quote! { #( #swap_code )* });
1121 } else if gate_checks.is_empty() {
1122 output.extend(child_body);
1124 output.extend(quote! { #( #swap_code )* });
1125 } else if is_root_loop {
1126 output.extend(quote! {
1128 #[allow(clippy::nonminimal_bool, reason = "codegen")]
1129 if false #( || #gate_checks )* {
1130 #child_body
1131 #( #swap_code )*
1132 }
1133 });
1134 } else {
1135 output.extend(quote! {
1137 #[allow(clippy::nonminimal_bool, reason = "codegen")]
1138 while false #( || #gate_checks )* {
1139 #child_body
1140 #( #swap_code )*
1141 }
1142 });
1143 }
1144 }
1145
1146 fn helper_loop_input_handoffs(&self) -> SecondaryMap<GraphLoopId, Vec<GraphNodeId>> {
1148 let mut loop_hoffs_inn = SecondaryMap::<GraphLoopId, Vec<GraphNodeId>>::new();
1149
1150 for (hoff_id, hoff) in self.nodes() {
1152 if !matches!(hoff, GraphNode::Handoff { .. }) {
1153 continue;
1154 }
1155
1156 let loop_pred = self
1158 .node_predecessors(hoff_id)
1159 .next()
1160 .and_then(|(_, pred)| self.node_loop(pred));
1161 let loop_succ = self
1162 .node_successors(hoff_id)
1163 .next()
1164 .and_then(|(_, succ)| self.node_loop(succ));
1165
1166 if let Some(loop_succ) = loop_succ
1167 && loop_pred == self.loop_parent(loop_succ)
1168 {
1169 loop_hoffs_inn
1171 .entry(loop_succ)
1172 .expect("loop removed")
1173 .or_default()
1174 .push(hoff_id);
1175 }
1176 }
1177
1178 loop_hoffs_inn
1179 }
1180
1181 pub fn as_code(
1196 &self,
1197 root: &TokenStream,
1198 include_type_guards: bool,
1199 prefix: TokenStream,
1200 diagnostics: &mut Diagnostics,
1201 ) -> Result<TokenStream, Diagnostics> {
1202 self.as_code_with_options(root, include_type_guards, true, prefix, diagnostics)
1203 }
1204
1205 pub fn as_code_with_options(
1214 &self,
1215 root: &TokenStream,
1216 include_type_guards: bool,
1217 include_meta: bool,
1218 prefix: TokenStream,
1219 diagnostics: &mut Diagnostics,
1220 ) -> Result<TokenStream, Diagnostics> {
1221 let df = Ident::new(GRAPH, Span::call_site());
1222 let context = Ident::new(CONTEXT, Span::call_site());
1223 let bump_ident = Ident::new("__dfir_bump", Span::call_site());
1225
1226 let handoff_nodes = self
1228 .nodes
1229 .iter()
1230 .filter_map(|(node_id, node)| match node {
1231 &GraphNode::Handoff {
1232 kind,
1233 src_span,
1234 dst_span,
1235 } => Some((node_id, kind, (src_span, dst_span))),
1236 GraphNode::Operator(_) => None,
1237 GraphNode::ModuleBoundary { .. } => panic!(),
1238 })
1239 .collect::<Vec<_>>();
1240
1241 let back_edge_hoffs_and_lazyness = handoff_nodes
1245 .iter()
1246 .map(|&(node_id, _, _)| node_id)
1247 .filter_map(|node_id| {
1248 let delay_type = self.handoff_delay_type(node_id)?;
1249 Some((
1250 node_id,
1251 matches!(delay_type, DelayType::TickLazy | DelayType::LoopLazy),
1252 ))
1253 })
1254 .collect::<SparseSecondaryMap<_, _>>();
1255
1256 let back_buffer_idents_laziness = handoff_nodes
1258 .iter()
1259 .filter_map(|&(hoff_id, _kind, (src_span, dst_span))| {
1260 back_edge_hoffs_and_lazyness.get(hoff_id).map(|&is_lazy| {
1261 let span = src_span.join(dst_span).unwrap_or(src_span);
1262 let back_ident = self.hoff_back_ident(hoff_id, span);
1263 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1264 (back_ident, buf_ident, is_lazy)
1265 })
1266 })
1267 .collect::<Vec<_>>();
1268
1269 let back_edge_swap_code = handoff_nodes
1276 .iter()
1277 .filter(|&&(node_id, _kind, _)| {
1278 self.handoff_delay_type(node_id)
1279 .is_some_and(|dt| matches!(dt, DelayType::Tick | DelayType::TickLazy))
1280 })
1281 .filter(|&&(hoff_id, _kind, _)| {
1282 let consumer_loop = self
1285 .node_successors(hoff_id)
1286 .next()
1287 .and_then(|(_, succ)| self.node_subgraph(succ))
1288 .and_then(|sg| self.subgraph_loop(sg));
1289 if let Some(loop_id) = consumer_loop {
1290 self.loop_parent(loop_id).is_some()
1292 } else {
1293 true
1295 }
1296 })
1297 .map(|&(hoff_id, _kind, _)| {
1298 let span = self.nodes[hoff_id].span();
1299 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1300 let back_ident = self.hoff_back_ident(hoff_id, span);
1301 quote_spanned! {span=>
1302 ::std::mem::swap(&mut #buf_ident, &mut #back_ident);
1303 }
1304 })
1305 .collect::<Vec<_>>();
1306
1307 let mut loop_swap_code: std::collections::HashMap<GraphLoopId, Vec<TokenStream>> =
1311 std::collections::HashMap::new();
1312 for &(hoff_id, _kind, _) in handoff_nodes.iter() {
1313 let Some(delay_type) = self.handoff_delay_type(hoff_id) else {
1314 continue;
1315 };
1316 let loop_id = self
1318 .node_successors(hoff_id)
1319 .next()
1320 .and_then(|(_, succ)| self.node_subgraph(succ))
1321 .and_then(|sg| self.subgraph_loop(sg));
1322 let Some(loop_id) = loop_id else {
1323 continue;
1324 };
1325 let include = match delay_type {
1326 DelayType::Loop | DelayType::LoopLazy => true,
1327 DelayType::Tick | DelayType::TickLazy => {
1328 self.loop_parent(loop_id).is_none()
1330 }
1331 };
1332 if !include {
1333 continue;
1334 }
1335 let span = self.nodes[hoff_id].span();
1336 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1337 let back_ident = self.hoff_back_ident(hoff_id, span);
1338 loop_swap_code
1339 .entry(loop_id)
1340 .or_default()
1341 .push(quote_spanned! {span=>
1342 ::std::mem::swap(&mut #buf_ident, &mut #back_ident);
1343 });
1344 }
1345
1346 let subgraph_handoffs = self.helper_collect_subgraph_handoffs();
1348
1349 let all_subgraphs: Vec<_> = self
1351 .subgraph_toposort()
1352 .iter()
1353 .map(|&sg_id| (sg_id, self.subgraph(sg_id)))
1354 .collect();
1355
1356 let mut op_prologue_code = Vec::new();
1360 let mut op_tick_end_code = Vec::new();
1361
1362 let mut loop_stack: Vec<(GraphLoopId, TokenStream)> = Vec::new();
1366 let mut current_output = TokenStream::new();
1367
1368 let loop_input_handoffs = self.helper_loop_input_handoffs();
1370 let loop_output_handoffs = self.helper_loop_output_handoffs();
1371
1372 {
1373 for &(subgraph_id, subgraph_nodes) in all_subgraphs.iter() {
1374 let sg_loop = self.subgraph_loop(subgraph_id);
1375
1376 while let Some(&(top_loop, _)) = loop_stack.last() {
1379 if sg_loop == Some(top_loop) || self.is_inside_loop(sg_loop, top_loop) {
1380 break;
1381 }
1382 let (closed_loop, child_body) = loop_stack.pop().unwrap();
1384 let target = if let Some((_, parent_body)) = loop_stack.last_mut() {
1385 parent_body
1386 } else {
1387 &mut current_output
1388 };
1389 self.emit_loop_gate(
1390 closed_loop,
1391 child_body,
1392 &loop_input_handoffs,
1393 &back_edge_hoffs_and_lazyness,
1394 &loop_swap_code,
1395 target,
1396 );
1397 }
1398
1399 if let Some(target_loop) = sg_loop
1401 && loop_stack.last().map(|&(l, _)| l) != Some(target_loop)
1402 {
1403 let mut path = Vec::new();
1405 let mut cur = Some(target_loop);
1406 while let Some(l) = cur {
1407 if loop_stack.last().map(|&(top, _)| top) == Some(l) {
1408 break;
1409 }
1410 path.push(l);
1411 cur = self.loop_parent(l);
1412 }
1413 for &loop_id in path.iter().rev() {
1416 if let Some(exit_hoffs) = loop_output_handoffs.get(loop_id) {
1418 let exit_hoff_decls = exit_hoffs.iter().map(|&hoff_id| {
1419 let span = self.nodes[hoff_id].span();
1420 let buf_ident = self.hoff_buf_ident(hoff_id, span);
1421 let GraphNode::Handoff { kind, .. } = self.node(hoff_id) else {
1422 panic!()
1423 };
1424 match kind {
1425 HandoffKind::Vec => quote_spanned! {span=>
1426 let mut #buf_ident = #root::bumpalo::collections::Vec::new_in(&#bump_ident);
1427 },
1428 HandoffKind::Singleton | HandoffKind::Optional => quote_spanned! {span=>
1429 let mut #buf_ident = ::std::option::Option::None;
1430 },
1431 }
1432 });
1433 let target = if let Some((_, body)) = loop_stack.last_mut() {
1434 body
1435 } else {
1436 &mut current_output
1437 };
1438 target.extend(quote! { #( #exit_hoff_decls )* });
1439 }
1440 loop_stack.push((loop_id, TokenStream::new()));
1441 }
1442 }
1443 let sg_metrics_ffi = subgraph_id.data().as_ffi();
1444 let (recv_hoffs, send_hoffs) = &subgraph_handoffs[subgraph_id];
1445
1446 let recv_port_idents: Vec<Ident> = recv_hoffs
1448 .iter()
1449 .map(|&hoff_id| self.node_as_ident(hoff_id, true))
1450 .collect();
1451 let send_port_idents: Vec<Ident> = send_hoffs
1452 .iter()
1453 .map(|&hoff_id| self.node_as_ident(hoff_id, false))
1454 .collect();
1455
1456 let recv_buf_idents: Vec<Ident> = recv_hoffs
1458 .iter()
1459 .map(|&hoff_id| self.hoff_buf_ident(hoff_id, self.nodes[hoff_id].span()))
1460 .collect();
1461 let send_buf_idents: Vec<Ident> = send_hoffs
1462 .iter()
1463 .map(|&hoff_id| self.hoff_buf_ident(hoff_id, self.nodes[hoff_id].span()))
1464 .collect();
1465
1466 let recv_kinds = recv_hoffs
1468 .iter()
1469 .map(|&hoff_id| {
1470 let GraphNode::Handoff { kind, .. } = self.node(hoff_id) else {
1471 panic!()
1472 };
1473 *kind
1474 })
1475 .collect::<Vec<_>>();
1476 let send_kinds = send_hoffs
1477 .iter()
1478 .map(|&hoff_id| {
1479 let GraphNode::Handoff { kind, .. } = self.node(hoff_id) else {
1480 panic!()
1481 };
1482 *kind
1483 })
1484 .collect::<Vec<_>>();
1485
1486 let recv_port_code: Vec<TokenStream> = recv_port_idents
1490 .iter()
1491 .zip(recv_buf_idents.iter())
1492 .zip(recv_kinds.iter())
1493 .zip(recv_hoffs.iter())
1494 .map(|(((port_ident, buf_ident), &kind), &hoff_id)| {
1495 let hoff_ffi = hoff_id.data().as_ffi();
1496 let work_done = Ident::new("__dfir_work_done", Span::call_site());
1500 let metrics = Ident::new("__dfir_metrics", Span::call_site());
1501
1502 let (len_expr, drain_expr) = match kind {
1504 HandoffKind::Singleton | HandoffKind::Optional => (
1505 quote! { if #buf_ident.is_some() { 1usize } else { 0usize } },
1506 quote! { #root::dfir_pipes::pull::iter(#buf_ident.take().into_iter()) },
1507 ),
1508 HandoffKind::Vec => {
1509 let drain_ident = if back_edge_hoffs_and_lazyness.contains_key(hoff_id) {
1513 &self.hoff_back_ident(hoff_id, buf_ident.span())
1514 } else {
1515 buf_ident
1516 };
1517 (
1518 quote! { #drain_ident.len() },
1519 quote! { #root::dfir_pipes::pull::iter(#drain_ident.drain(..)) },
1520 )
1521 }
1522 };
1523
1524 quote_spanned! {port_ident.span()=>
1525 {
1526 let hoff_len = #len_expr;
1527 if hoff_len > 0 {
1528 #work_done = true;
1529 }
1530 let hoff_metrics = &#metrics.handoffs[
1531 #root::slotmap::KeyData::from_ffi(#hoff_ffi).into()
1532 ];
1533 hoff_metrics.total_items_count.update(|x| x + hoff_len);
1534 hoff_metrics.curr_items_count.set(hoff_len);
1535 }
1536 let #port_ident = #drain_expr;
1537 }
1538 })
1539 .collect();
1540
1541 let send_port_code: Vec<TokenStream> = send_port_idents
1543 .iter()
1544 .zip(send_buf_idents.iter())
1545 .zip(send_kinds.iter())
1546 .map(|((port_ident, buf_ident), &kind)| {
1547 match kind {
1548 HandoffKind::Singleton => {
1549 quote_spanned! {port_ident.span()=>
1551 let #port_ident = #root::dfir_pipes::push::for_each(|__item| {
1552 if #buf_ident.replace(__item).is_some() {
1553 panic!("singleton() received more than one item");
1554 }
1555 });
1556 }
1557 }
1558 HandoffKind::Optional => {
1559 quote_spanned! {port_ident.span()=>
1561 let #port_ident = #root::dfir_pipes::push::for_each(|__item| {
1562 if #buf_ident.replace(__item).is_some() {
1563 panic!("optional() received more than one item");
1564 }
1565 });
1566 }
1567 }
1568 HandoffKind::Vec => {
1569 quote_spanned! {port_ident.span()=>
1570 let #port_ident = #root::dfir_pipes::push::for_each(|item| { #buf_ident.push(item); });
1572 }
1573 }
1574 }
1575 })
1576 .collect();
1577
1578 let loop_id = self.node_loop(subgraph_nodes[0]);
1580
1581 let mut subgraph_op_iter_code = Vec::new();
1582 let mut subgraph_op_iter_after_code = Vec::new();
1583 {
1584 let pull_to_push_idx = self.find_pull_to_push_idx(subgraph_nodes);
1585
1586 let (pull_half, push_half) = subgraph_nodes.split_at(pull_to_push_idx);
1587 let nodes_iter = pull_half.iter().chain(push_half.iter().rev());
1588
1589 for (idx, &node_id) in nodes_iter.enumerate() {
1590 let node = &self.nodes[node_id];
1591 assert!(
1592 matches!(node, GraphNode::Operator(_)),
1593 "Handoffs are not part of subgraphs."
1594 );
1595 let op_inst = &self.operator_instances[node_id];
1596
1597 let op_span = node.span();
1598 let op_name = op_inst.op_constraints.name;
1599 let root = change_spans(root.clone(), op_span);
1601 let op_constraints = OPERATORS
1602 .iter()
1603 .find(|op| op_name == op.name)
1604 .unwrap_or_else(|| panic!("Failed to find op: {}", op_name));
1605
1606 let ident = self.node_as_ident(node_id, false);
1607
1608 {
1609 let mut input_edges = self
1612 .graph
1613 .predecessor_edges(node_id)
1614 .map(|edge_id| (self.edge_ports(edge_id).1, edge_id))
1615 .collect::<Vec<_>>();
1616 input_edges.sort();
1618
1619 let inputs = input_edges
1620 .iter()
1621 .map(|&(_port, edge_id)| {
1622 let (pred, _) = self.edge(edge_id);
1623 self.node_as_ident(pred, true)
1624 })
1625 .collect::<Vec<_>>();
1626
1627 let mut output_edges = self
1629 .graph
1630 .successor_edges(node_id)
1631 .map(|edge_id| (&self.ports[edge_id].0, edge_id))
1632 .collect::<Vec<_>>();
1633 output_edges.sort();
1635
1636 let outputs = output_edges
1637 .iter()
1638 .map(|&(_port, edge_id)| {
1639 let (_, succ) = self.edge(edge_id);
1640 self.node_as_ident(succ, false)
1641 })
1642 .collect::<Vec<_>>();
1643
1644 let is_pull = idx < pull_to_push_idx;
1645
1646 let df_local = &Ident::new(GRAPH, op_span.resolved_at(df.span()));
1655 let context = &Ident::new(CONTEXT, op_span.resolved_at(context.span()));
1656
1657 let singletons_resolved =
1658 self.helper_resolve_singletons(node_id, op_span);
1659
1660 let arguments = &process_singletons::postprocess_singletons(
1661 op_inst.arguments_raw.clone(),
1662 singletons_resolved,
1663 );
1664
1665 let source_tag = 'a: {
1666 if let Some(tag) = self.operator_tag.get(node_id).cloned() {
1667 break 'a tag;
1668 }
1669
1670 if proc_macro::is_available() {
1671 let op_span = op_span.unwrap();
1672 break 'a format!(
1673 "loc_{}_{}_{}_{}_{}",
1674 crate::pretty_span::make_source_path_relative(
1675 &op_span.file()
1676 )
1677 .display()
1678 .to_string()
1679 .replace(|x: char| !x.is_ascii_alphanumeric(), "_"),
1680 op_span.start().line(),
1681 op_span.start().column(),
1682 op_span.end().line(),
1683 op_span.end().column(),
1684 );
1685 }
1686
1687 format!(
1688 "loc_nopath_{}_{}_{}_{}",
1689 op_span.start().line,
1690 op_span.start().column,
1691 op_span.end().line,
1692 op_span.end().column
1693 )
1694 };
1695
1696 let work_fn = format_ident!(
1697 "{}__{}__{}",
1698 ident,
1699 op_name,
1700 source_tag,
1701 span = op_span
1702 );
1703 let work_fn_async = format_ident!("{}__async", work_fn, span = op_span);
1704
1705 let context_args = WriteContextArgs {
1706 root: &root,
1707 df_ident: df_local,
1708 context,
1709 subgraph_id,
1710 node_id,
1711 loop_id,
1712 op_span,
1713 op_tag: self.operator_tag.get(node_id).cloned(),
1714 work_fn: &work_fn,
1715 work_fn_async: &work_fn_async,
1716 ident: &ident,
1717 is_pull,
1718 inputs: &inputs,
1719 outputs: &outputs,
1720 op_name,
1721 op_inst,
1722 arguments,
1723 };
1724
1725 let write_result =
1726 (op_constraints.write_fn)(&context_args, diagnostics);
1727 let OperatorWriteOutput {
1728 write_prologue,
1729 write_iterator,
1730 write_iterator_after,
1731 write_tick_end,
1732 } = write_result.unwrap_or_else(|()| {
1733 assert!(
1734 diagnostics.has_error(),
1735 "Operator `{}` returned `Err` but emitted no diagnostics, this is a bug.",
1736 op_name,
1737 );
1738 OperatorWriteOutput {
1739 write_iterator: null_write_iterator_fn(&context_args),
1740 ..Default::default()
1741 }
1742 });
1743
1744 op_prologue_code.push(syn::parse_quote! {
1745 #[allow(dead_code, non_snake_case, reason = "codegen")]
1746 #[inline(always)]
1747 fn #work_fn<T>(thunk: impl ::std::ops::FnOnce() -> T) -> T {
1748 thunk()
1749 }
1750
1751 #[allow(dead_code, non_snake_case, reason = "codegen")]
1752 #[inline(always)]
1753 async fn #work_fn_async<T>(
1754 thunk: impl ::std::future::Future<Output = T>,
1755 ) -> T {
1756 thunk.await
1757 }
1758 });
1759 op_prologue_code.push(write_prologue);
1760 op_tick_end_code.push(write_tick_end);
1761 subgraph_op_iter_code.push(write_iterator);
1762
1763 if include_type_guards {
1764 let type_guard = if is_pull {
1765 quote_spanned! {op_span=>
1766 let #ident = {
1767 #[allow(non_snake_case)]
1768 #[inline(always)]
1769 pub fn #work_fn<Item, Input>(input: Input)
1770 -> impl #root::dfir_pipes::pull::Pull<Item = Item, Meta = (), CanPend = Input::CanPend, CanEnd = Input::CanEnd>
1771 where
1772 Input: #root::dfir_pipes::pull::Pull<Item = Item, Meta = ()>,
1773 {
1774 #root::pin_project_lite::pin_project! {
1775 #[repr(transparent)]
1776 struct Pull<Item, Input: #root::dfir_pipes::pull::Pull<Item = Item>> {
1777 #[pin]
1778 inner: Input
1779 }
1780 }
1781
1782 impl<Item, Input> #root::dfir_pipes::pull::Pull for Pull<Item, Input>
1783 where
1784 Input: #root::dfir_pipes::pull::Pull<Item = Item>,
1785 {
1786 type Ctx<'ctx> = Input::Ctx<'ctx>;
1787
1788 type Item = Item;
1789 type Meta = Input::Meta;
1790 type CanPend = Input::CanPend;
1791 type CanEnd = Input::CanEnd;
1792
1793 #[inline(always)]
1794 fn pull(
1795 self: ::std::pin::Pin<&mut Self>,
1796 ctx: &mut Self::Ctx<'_>,
1797 ) -> #root::dfir_pipes::pull::PullStep<Self::Item, Self::Meta, Self::CanPend, Self::CanEnd> {
1798 #root::dfir_pipes::pull::Pull::pull(self.project().inner, ctx)
1799 }
1800
1801 #[inline(always)]
1802 fn size_hint(&self) -> (usize, Option<usize>) {
1803 #root::dfir_pipes::pull::Pull::size_hint(&self.inner)
1804 }
1805 }
1806
1807 Pull {
1808 inner: input
1809 }
1810 }
1811 #work_fn::<_, _>( #ident )
1812 };
1813 }
1814 } else {
1815 quote_spanned! {op_span=>
1816 let #ident = {
1817 #[allow(non_snake_case)]
1818 #[inline(always)]
1819 pub fn #work_fn<Item, Psh>(psh: Psh) -> impl #root::dfir_pipes::push::Push<Item, (), CanPend = Psh::CanPend>
1820 where
1821 Psh: #root::dfir_pipes::push::Push<Item, ()>
1822 {
1823 #root::pin_project_lite::pin_project! {
1824 #[repr(transparent)]
1825 struct PushGuard<Psh> {
1826 #[pin]
1827 inner: Psh,
1828 }
1829 }
1830
1831 impl<Item, Psh> #root::dfir_pipes::push::Push<Item, ()> for PushGuard<Psh>
1832 where
1833 Psh: #root::dfir_pipes::push::Push<Item, ()>,
1834 {
1835 type Ctx<'ctx> = Psh::Ctx<'ctx>;
1836
1837 type CanPend = Psh::CanPend;
1838
1839 #[inline(always)]
1840 fn poll_ready(
1841 self: ::std::pin::Pin<&mut Self>,
1842 ctx: &mut Self::Ctx<'_>,
1843 ) -> #root::dfir_pipes::push::PushStep<Self::CanPend> {
1844 #root::dfir_pipes::push::Push::poll_ready(self.project().inner, ctx)
1845 }
1846
1847 #[inline(always)]
1848 fn start_send(
1849 self: ::std::pin::Pin<&mut Self>,
1850 item: Item,
1851 meta: (),
1852 ) {
1853 #root::dfir_pipes::push::Push::start_send(self.project().inner, item, meta)
1854 }
1855
1856 #[inline(always)]
1857 fn poll_finalize(
1858 self: ::std::pin::Pin<&mut Self>,
1859 ctx: &mut Self::Ctx<'_>,
1860 ) -> #root::dfir_pipes::push::PushStep<Self::CanPend> {
1861 #root::dfir_pipes::push::Push::poll_finalize(self.project().inner, ctx)
1862 }
1863
1864 #[inline(always)]
1865 fn size_hint(
1866 self: ::std::pin::Pin<&mut Self>,
1867 hint: (usize, Option<usize>),
1868 ) {
1869 #root::dfir_pipes::push::Push::size_hint(self.project().inner, hint)
1870 }
1871 }
1872
1873 PushGuard {
1874 inner: psh
1875 }
1876 }
1877 #work_fn( #ident )
1878 };
1879 }
1880 };
1881 subgraph_op_iter_code.push(type_guard);
1882 }
1883 subgraph_op_iter_after_code.push(write_iterator_after);
1884 }
1885 }
1886
1887 {
1888 let pull_ident = if 0 < pull_to_push_idx {
1890 self.node_as_ident(subgraph_nodes[pull_to_push_idx - 1], false)
1891 } else {
1892 recv_port_idents[0].clone()
1894 };
1895
1896 #[rustfmt::skip]
1897 let push_ident = if let Some(&node_id) =
1898 subgraph_nodes.get(pull_to_push_idx)
1899 {
1900 self.node_as_ident(node_id, false)
1901 } else if 1 == send_port_idents.len() {
1902 send_port_idents[0].clone()
1904 } else {
1905 diagnostics.push(Diagnostic::spanned(
1906 pull_ident.span(),
1907 Level::Error,
1908 "Degenerate subgraph detected, is there a disconnected `null()` or other degenerate pipeline somewhere?",
1909 ));
1910 continue;
1911 };
1912
1913 let pivot_span = pull_ident
1915 .span()
1916 .join(push_ident.span())
1917 .unwrap_or_else(|| push_ident.span());
1918 let pivot_fn_ident = Ident::new(
1919 &format!("pivot_run_sg_{:?}", subgraph_id.data()),
1920 pivot_span,
1921 );
1922 let root = change_spans(root.clone(), pivot_span);
1923 subgraph_op_iter_code.push(quote_spanned! {pivot_span=>
1924 #[inline(always)]
1925 fn #pivot_fn_ident<Pul, Psh, Item>(pull: Pul, push: Psh)
1926 -> impl ::std::future::Future<Output = ()>
1927 where
1928 Pul: #root::dfir_pipes::pull::Pull<Item = Item>,
1929 Psh: #root::dfir_pipes::push::Push<Item, Pul::Meta>,
1930 {
1931 #root::dfir_pipes::pull::Pull::send_push(pull, push)
1932 }
1933 (#pivot_fn_ident)(#pull_ident, #push_ident).await;
1934 });
1935 }
1936 };
1937
1938 let sg_fut_ident = subgraph_id.as_ident(Span::call_site());
1942
1943 let send_metrics_code = send_hoffs
1945 .iter()
1946 .zip(send_buf_idents.iter())
1947 .zip(send_kinds.iter())
1948 .map(|((&hoff_id, buf_ident), &kind)| {
1949 let hoff_ffi = hoff_id.data().as_ffi();
1950 let len_expr = match kind {
1951 HandoffKind::Singleton | HandoffKind::Optional => {
1952 quote! { if #buf_ident.is_some() { 1 } else { 0 } }
1953 }
1954 HandoffKind::Vec => {
1955 quote! { #buf_ident.len() }
1956 }
1957 };
1958 quote! {
1959 __dfir_metrics.handoffs[
1960 #root::slotmap::KeyData::from_ffi(#hoff_ffi).into()
1961 ].curr_items_count.set(#len_expr);
1962 }
1963 })
1964 .collect::<Vec<_>>();
1965
1966 let send_hoff_make_code = send_buf_idents.iter()
1970 .zip(send_kinds.iter())
1971 .zip(send_hoffs.iter())
1972 .filter_map(|((buf_ident, &kind), &hoff_id)| {
1973 let span = buf_ident.span();
1974 if back_edge_hoffs_and_lazyness.contains_key(hoff_id) {
1975 Some(quote_spanned! {span=>
1978 #buf_ident.clear();
1979 })
1980 } else {
1981 let receiver_loop = self
1984 .node_successors(hoff_id)
1985 .next()
1986 .and_then(|(_, succ)| self.node_loop(succ));
1987 let is_exit = if let Some(sender_loop) = sg_loop {
1988 receiver_loop == self.loop_parent(sender_loop)
1989 } else {
1990 false
1991 };
1992 if is_exit {
1993 None
1995 } else {
1996 Some(match kind {
1997 HandoffKind::Vec => quote_spanned! {span=>
1998 let mut #buf_ident = #root::bumpalo::collections::Vec::new_in(&#bump_ident);
1999 },
2000 HandoffKind::Singleton | HandoffKind::Optional => quote_spanned! {span=>
2001 let mut #buf_ident = ::std::option::Option::None;
2002 },
2003 })
2004 }
2005 }
2006 })
2007 .collect::<Vec<_>>();
2008 let recv_hoff_drop_code = recv_buf_idents
2012 .iter()
2013 .zip(recv_hoffs.iter())
2014 .filter(|&(_, &hoff_id)| !back_edge_hoffs_and_lazyness.contains_key(hoff_id))
2015 .map(|(buf_ident, _)| {
2016 let span = buf_ident.span();
2017 quote_spanned! {span=>
2018 let _ = #buf_ident;
2019 }
2020 });
2021
2022 let sg_block = quote! {
2024 #( #send_hoff_make_code )*
2026
2027 let #sg_fut_ident = async {
2028 let #context = &#df;
2029 #( #recv_port_code )*
2030 #( #send_port_code )*
2031 #( #subgraph_op_iter_code )*
2032 #( #subgraph_op_iter_after_code )*
2033 };
2034 {
2035 let sg_metrics = &__dfir_metrics.subgraphs[
2037 #root::slotmap::KeyData::from_ffi(#sg_metrics_ffi).into()
2038 ];
2039 #root::scheduled::metrics::InstrumentSubgraph::new(
2040 #sg_fut_ident, sg_metrics
2041 ).await;
2042 sg_metrics.total_run_count.update(|x| x + 1);
2043
2044 #( #send_metrics_code )*
2046
2047 #( #recv_hoff_drop_code )*
2049 }
2050 };
2051 if let Some((_, body)) = loop_stack.last_mut() {
2052 body.extend(sg_block);
2053 } else {
2054 current_output.extend(sg_block);
2055 }
2056 }
2057 }
2058
2059 let gated_subgraph_code = {
2061 while let Some((closed_loop, child_body)) = loop_stack.pop() {
2062 let target = if let Some((_, parent_body)) = loop_stack.last_mut() {
2063 parent_body
2064 } else {
2065 &mut current_output
2066 };
2067 self.emit_loop_gate(
2068 closed_loop,
2069 child_body,
2070 &loop_input_handoffs,
2071 &back_edge_hoffs_and_lazyness,
2072 &loop_swap_code,
2073 target,
2074 );
2075 }
2076 current_output
2077 };
2078
2079 if diagnostics.has_error() {
2080 return Err(std::mem::take(diagnostics));
2081 }
2082 let _ = diagnostics; let (meta_graph_arg, diagnostics_arg) = if include_meta {
2085 let meta_graph_json = serde_json::to_string(&self).unwrap();
2086 let meta_graph_json = Literal::string(&meta_graph_json);
2087
2088 let serde_diagnostics: Vec<_> = diagnostics.iter().map(Diagnostic::to_serde).collect();
2089 let diagnostics_json = serde_json::to_string(&*serde_diagnostics).unwrap();
2090 let diagnostics_json = Literal::string(&diagnostics_json);
2091
2092 (
2093 quote! { Some(#meta_graph_json) },
2094 quote! { Some(#diagnostics_json) },
2095 )
2096 } else {
2097 (quote! { None }, quote! { None })
2098 };
2099
2100 let metrics_init_code = {
2102 let handoff_inits = handoff_nodes.iter().map(|&(node_id, _, _)| {
2103 let ffi = node_id.data().as_ffi();
2104 quote! {
2105 dfir_metrics.handoffs.insert(
2106 #root::slotmap::KeyData::from_ffi(#ffi).into(),
2107 ::std::default::Default::default(),
2108 );
2109 }
2110 });
2111 let subgraph_inits = all_subgraphs.iter().map(|&(sg_id, _)| {
2112 let ffi = sg_id.data().as_ffi();
2113 quote! {
2114 dfir_metrics.subgraphs.insert(
2115 #root::slotmap::KeyData::from_ffi(#ffi).into(),
2116 ::std::default::Default::default(),
2117 );
2118 }
2119 });
2120 handoff_inits.chain(subgraph_inits).collect::<Vec<_>>()
2121 };
2122
2123 let back_buffer_idents = back_buffer_idents_laziness
2125 .iter()
2126 .map(|(back_ident, _, _)| back_ident);
2127 let defer_tick_buf_idents = back_buffer_idents_laziness
2129 .iter()
2130 .map(|(_, buf_ident, _)| buf_ident);
2131 let non_lazy_schedule_idents: Vec<&Ident> = handoff_nodes
2136 .iter()
2137 .filter_map(|&(hoff_id, _, _)| {
2138 let delay_type = self.handoff_delay_type(hoff_id)?;
2139 if matches!(delay_type, DelayType::TickLazy | DelayType::LoopLazy) {
2141 return None;
2142 }
2143 let span = self.nodes[hoff_id].span();
2144 let expected_back_ident = self.hoff_back_ident(hoff_id, span);
2145 let entry = back_buffer_idents_laziness
2146 .iter()
2147 .find(|(back_ident, _, _)| *back_ident == expected_back_ident)?;
2148
2149 if delay_type == DelayType::Tick {
2151 let consumer_loop = self
2152 .node_successors(hoff_id)
2153 .next()
2154 .and_then(|(_, succ)| self.node_subgraph(succ))
2155 .and_then(|sg| self.subgraph_loop(sg));
2156 if consumer_loop.is_some_and(|lid| self.loop_parent(lid).is_none()) {
2157 return Some(&entry.0); }
2159 }
2160 Some(&entry.1) })
2162 .collect();
2163
2164 Ok(quote! {
2167 {
2168 #prefix
2169
2170 use #root::{var_expr, var_args};
2171
2172 let __dfir_wake_state = ::std::sync::Arc::new(
2173 #root::scheduled::context::WakeState::default()
2174 );
2175
2176 let __dfir_metrics = {
2177 let mut dfir_metrics = #root::scheduled::metrics::DfirMetrics::default();
2178 #( #metrics_init_code )*
2179 ::std::rc::Rc::new(dfir_metrics)
2180 };
2181
2182 #[allow(unused_mut)]
2183 let mut #df = #root::scheduled::context::Context::new(
2184 ::std::clone::Clone::clone(&__dfir_wake_state),
2185 __dfir_metrics,
2186 );
2187
2188 #( #op_prologue_code )*
2189
2190 #( let mut #back_buffer_idents = ::std::vec::Vec::new(); )*
2194 #( let mut #defer_tick_buf_idents = ::std::vec::Vec::new(); )*
2195
2196 let mut #bump_ident = #root::bumpalo::Bump::new();
2198
2199 let mut __dfir_work_done = true;
2204 #[allow(unused_qualifications, unused_mut, unused_variables, clippy::await_holding_refcell_ref, clippy::deref_addrof)]
2205 let __dfir_inline_tick = async move |#df: &mut #root::scheduled::context::Context| {
2206 #bump_ident.reset();
2208
2209 {
2210 let __dfir_metrics = #df.metrics();
2211
2212 #gated_subgraph_code
2213
2214 #[allow(clippy::nonminimal_bool, reason = "codegen")]
2217 if false #( || !#non_lazy_schedule_idents.is_empty() )* {
2218 #df.schedule_subgraph(true);
2219 }
2220
2221 #( #back_edge_swap_code )*
2224 }
2225
2226 #( #op_tick_end_code )*
2228
2229 #df.__end_tick();
2230
2231 ::std::mem::take(&mut __dfir_work_done)
2232 };
2233 #root::scheduled::context::Dfir::new(
2234 __dfir_inline_tick,
2235 #df,
2236 #meta_graph_arg,
2237 #diagnostics_arg,
2238 )
2239 }
2240 })
2241 }
2242
2243 pub fn node_color_map(&self) -> SparseSecondaryMap<GraphNodeId, Color> {
2246 let mut node_color_map: SparseSecondaryMap<GraphNodeId, Color> = self
2247 .node_ids()
2248 .filter_map(|node_id| {
2249 let op_color = self.node_color(node_id)?;
2250 Some((node_id, op_color))
2251 })
2252 .collect();
2253
2254 for sg_nodes in self.subgraph_nodes.values() {
2256 let pull_to_push_idx = self.find_pull_to_push_idx(sg_nodes);
2257
2258 for (idx, node_id) in sg_nodes.iter().copied().enumerate() {
2259 let is_pull = idx < pull_to_push_idx;
2260 node_color_map.insert(node_id, if is_pull { Color::Pull } else { Color::Push });
2261 }
2262 }
2263
2264 node_color_map
2265 }
2266
2267 pub fn to_mermaid(&self, write_config: &WriteConfig) -> String {
2269 let mut output = String::new();
2270 self.write_mermaid(&mut output, write_config).unwrap();
2271 output
2272 }
2273
2274 pub fn write_mermaid(
2276 &self,
2277 output: impl std::fmt::Write,
2278 write_config: &WriteConfig,
2279 ) -> std::fmt::Result {
2280 let mut graph_write = Mermaid::new(output);
2281 self.write_graph(&mut graph_write, write_config)
2282 }
2283
2284 pub fn to_dot(&self, write_config: &WriteConfig) -> String {
2286 let mut output = String::new();
2287 let mut graph_write = Dot::new(&mut output);
2288 self.write_graph(&mut graph_write, write_config).unwrap();
2289 output
2290 }
2291
2292 pub fn write_dot(
2294 &self,
2295 output: impl std::fmt::Write,
2296 write_config: &WriteConfig,
2297 ) -> std::fmt::Result {
2298 let mut graph_write = Dot::new(output);
2299 self.write_graph(&mut graph_write, write_config)
2300 }
2301
2302 pub(crate) fn write_graph<W>(
2304 &self,
2305 mut graph_write: W,
2306 write_config: &WriteConfig,
2307 ) -> Result<(), W::Err>
2308 where
2309 W: GraphWrite,
2310 {
2311 fn helper_edge_label(
2312 src_port: &PortIndexValue,
2313 dst_port: &PortIndexValue,
2314 ) -> Option<String> {
2315 let src_label = match src_port {
2316 PortIndexValue::Path(path) => Some(path.to_token_stream().to_string()),
2317 PortIndexValue::Int(index) => Some(index.value.to_string()),
2318 _ => None,
2319 };
2320 let dst_label = match dst_port {
2321 PortIndexValue::Path(path) => Some(path.to_token_stream().to_string()),
2322 PortIndexValue::Int(index) => Some(index.value.to_string()),
2323 _ => None,
2324 };
2325 let label = match (src_label, dst_label) {
2326 (Some(l1), Some(l2)) => Some(format!("{}\n{}", l1, l2)),
2327 (Some(l1), None) => Some(l1),
2328 (None, Some(l2)) => Some(l2),
2329 (None, None) => None,
2330 };
2331 label
2332 }
2333
2334 let node_color_map = self.node_color_map();
2336
2337 graph_write.write_prologue()?;
2339
2340 let mut skipped_handoffs = BTreeSet::new();
2342 for (node_id, node) in self.nodes() {
2343 if matches!(node, GraphNode::Handoff { .. }) && write_config.no_handoffs {
2344 skipped_handoffs.insert(node_id);
2345 continue;
2346 }
2347 graph_write.write_node_definition(
2348 node_id,
2349 &if write_config.op_short_text {
2350 node.to_name_string()
2351 } else if write_config.op_text_no_imports {
2352 let full_text = node.to_pretty_string();
2354 let mut output = String::new();
2355 for sentence in full_text.split('\n') {
2356 if sentence.trim().starts_with("use") {
2357 continue;
2358 }
2359 output.push('\n');
2360 output.push_str(sentence);
2361 }
2362 output.into()
2363 } else {
2364 node.to_pretty_string()
2365 },
2366 if write_config.no_pull_push {
2367 None
2368 } else {
2369 node_color_map.get(node_id).copied()
2370 },
2371 )?;
2372 }
2373
2374 for (edge_id, (src_id, mut dst_id)) in self.edges() {
2376 if skipped_handoffs.contains(&src_id) {
2378 continue;
2379 }
2380
2381 let (src_port, mut dst_port) = self.edge_ports(edge_id);
2382 if skipped_handoffs.contains(&dst_id) {
2383 let mut handoff_succs = self.node_successors(dst_id);
2387 if handoff_succs.len() == 0 {
2388 continue;
2389 }
2390 let (succ_edge, succ_node) = handoff_succs.next().unwrap();
2391 dst_id = succ_node;
2392 dst_port = self.edge_ports(succ_edge).1;
2393 }
2394
2395 let label = helper_edge_label(src_port, dst_port);
2396 let delay_type = self
2397 .node_op_inst(dst_id)
2398 .and_then(|op_inst| (op_inst.op_constraints.input_delaytype_fn)(dst_port));
2399 graph_write.write_edge(src_id, dst_id, delay_type, label.as_deref(), false)?;
2400 }
2401
2402 if !write_config.no_references {
2404 for dst_id in self.node_ids() {
2405 for src_ref_id in self
2406 .node_handoff_references(dst_id)
2407 .iter()
2408 .filter_map(|r| r.node_id)
2409 {
2410 let resolved_src = if skipped_handoffs.contains(&src_ref_id) {
2413 self.node_predecessor_nodes(src_ref_id).next()
2414 } else {
2415 Some(src_ref_id)
2416 };
2417 let Some(resolved_src) = resolved_src else {
2418 continue;
2419 };
2420 let label = None;
2421 graph_write.write_edge(resolved_src, dst_id, None, label, true)?;
2422 }
2423 }
2424 }
2425
2426 let loop_subgraphs = self.subgraph_ids().map(|sg_id| {
2434 let loop_id = if write_config.no_loops {
2435 None
2436 } else {
2437 self.subgraph_loop(sg_id)
2438 };
2439 (loop_id, sg_id)
2440 });
2441 let loop_subgraphs = into_group_map(loop_subgraphs);
2442 for (loop_id, subgraph_ids) in loop_subgraphs {
2443 if let Some(loop_id) = loop_id {
2444 graph_write.write_loop_start(loop_id)?;
2445 }
2446
2447 let subgraph_varnames_nodes = subgraph_ids.into_iter().flat_map(|sg_id| {
2449 self.subgraph(sg_id).iter().copied().map(move |node_id| {
2450 let opt_sg_id = if write_config.no_subgraphs {
2451 None
2452 } else {
2453 Some(sg_id)
2454 };
2455 (opt_sg_id, (self.node_varname(node_id), node_id))
2456 })
2457 });
2458 let subgraph_varnames_nodes = into_group_map(subgraph_varnames_nodes);
2459 for (sg_id, varnames) in subgraph_varnames_nodes {
2460 if let Some(sg_id) = sg_id {
2461 graph_write.write_subgraph_start(sg_id)?;
2462 }
2463
2464 let varname_nodes = varnames.into_iter().map(|(varname, node)| {
2466 let varname = if write_config.no_varnames {
2467 None
2468 } else {
2469 varname
2470 };
2471 (varname, node)
2472 });
2473 let varname_nodes = into_group_map(varname_nodes);
2474 for (varname, node_ids) in varname_nodes {
2475 if let Some(varname) = varname {
2476 graph_write.write_varname_start(&varname.0.to_string(), sg_id)?;
2477 }
2478
2479 for node_id in node_ids {
2481 graph_write.write_node(node_id)?;
2482 }
2483
2484 if varname.is_some() {
2485 graph_write.write_varname_end()?;
2486 }
2487 }
2488
2489 if sg_id.is_some() {
2490 graph_write.write_subgraph_end()?;
2491 }
2492 }
2493
2494 if loop_id.is_some() {
2495 graph_write.write_loop_end()?;
2496 }
2497 }
2498
2499 graph_write.write_epilogue()?;
2501
2502 Ok(())
2503 }
2504
2505 pub fn surface_syntax_string(&self) -> String {
2507 let mut string = String::new();
2508 self.write_surface_syntax(&mut string).unwrap();
2509 string
2510 }
2511
2512 pub fn write_surface_syntax(&self, write: &mut impl std::fmt::Write) -> std::fmt::Result {
2514 for (key, node) in self.nodes.iter() {
2515 match node {
2516 GraphNode::Operator(op) => {
2517 writeln!(write, "_{:?} = {};", key.data(), op.to_token_stream())?;
2518 }
2519 GraphNode::Handoff {
2520 kind: HandoffKind::Vec,
2521 ..
2522 } => {
2523 writeln!(write, "_{:?} = handoff();", key.data())?;
2524 }
2525 GraphNode::Handoff {
2526 kind: HandoffKind::Singleton,
2527 ..
2528 } => {
2529 writeln!(write, "_{:?} = singleton();", key.data())?;
2530 }
2531 GraphNode::Handoff {
2532 kind: HandoffKind::Optional,
2533 ..
2534 } => {
2535 writeln!(write, "_{:?} = optional();", key.data())?;
2536 }
2537 GraphNode::ModuleBoundary { .. } => panic!(),
2538 }
2539 }
2540 writeln!(write)?;
2541 for (e, (src_key, dst_key)) in self.graph.edges() {
2542 let (src_port, dst_port) = self.edge_ports(e);
2543 let src_port_str = if src_port.is_specified() {
2544 format!("[{}]", src_port)
2545 } else {
2546 String::new()
2547 };
2548 let dst_port_str = if dst_port.is_specified() {
2549 format!("[{}]", dst_port)
2550 } else {
2551 String::new()
2552 };
2553 writeln!(
2554 write,
2555 "_{:?}{} -> {}_{:?};",
2556 src_key.data(),
2557 src_port_str,
2558 dst_port_str,
2559 dst_key.data()
2560 )?;
2561 }
2562 Ok(())
2563 }
2564
2565 pub fn mermaid_string_flat(&self) -> String {
2567 let mut string = String::new();
2568 self.write_mermaid_flat(&mut string).unwrap();
2569 string
2570 }
2571
2572 pub fn write_mermaid_flat(&self, write: &mut impl std::fmt::Write) -> std::fmt::Result {
2574 writeln!(write, "flowchart TB")?;
2575 for (key, node) in self.nodes.iter() {
2576 match node {
2577 GraphNode::Operator(operator) => writeln!(
2578 write,
2579 " %% {span}\n {id:?}[\"{row_col} <tt>{code}</tt>\"]",
2580 span = PrettySpan(node.span()),
2581 id = key.data(),
2582 row_col = PrettyRowCol(node.span()),
2583 code = operator
2584 .to_token_stream()
2585 .to_string()
2586 .replace('&', "&")
2587 .replace('<', "<")
2588 .replace('>', ">")
2589 .replace('"', """)
2590 .replace('\n', "<br>"),
2591 ),
2592 GraphNode::Handoff {
2593 kind: HandoffKind::Vec,
2594 ..
2595 } => {
2596 writeln!(write, r#" {:?}{{"{}"}}"#, key.data(), HANDOFF_NODE_STR)
2597 }
2598 GraphNode::Handoff {
2599 kind: HandoffKind::Singleton | HandoffKind::Optional,
2600 ..
2601 } => {
2602 writeln!(
2603 write,
2604 r#" {:?}{{"{}"}}"#,
2605 key.data(),
2606 SINGLETON_SLOT_NODE_STR
2607 )
2608 }
2609 GraphNode::ModuleBoundary { .. } => {
2610 writeln!(
2611 write,
2612 r#" {:?}{{"{}"}}"#,
2613 key.data(),
2614 MODULE_BOUNDARY_NODE_STR
2615 )
2616 }
2617 }?;
2618 }
2619 writeln!(write)?;
2620 for (_e, (src_key, dst_key)) in self.graph.edges() {
2621 writeln!(write, " {:?}-->{:?}", src_key.data(), dst_key.data())?;
2622 }
2623 Ok(())
2624 }
2625}
2626
2627impl DfirGraph {
2629 pub fn loop_ids(&self) -> slotmap::basic::Keys<'_, GraphLoopId, Vec<GraphNodeId>> {
2631 self.loop_nodes.keys()
2632 }
2633
2634 pub fn loops(&self) -> slotmap::basic::Iter<'_, GraphLoopId, Vec<GraphNodeId>> {
2636 self.loop_nodes.iter()
2637 }
2638
2639 pub fn loop_nodes(&self, loop_id: GraphLoopId) -> &[GraphNodeId] {
2641 self.loop_nodes.get(loop_id).unwrap()
2642 }
2643
2644 pub fn insert_loop(&mut self, parent_loop: Option<GraphLoopId>) -> GraphLoopId {
2646 let loop_id = self.loop_nodes.insert(Vec::new());
2647 self.loop_children.insert(loop_id, Vec::new());
2648 if let Some(parent_loop) = parent_loop {
2649 self.loop_parent.insert(loop_id, parent_loop);
2650 self.loop_children
2651 .get_mut(parent_loop)
2652 .unwrap()
2653 .push(loop_id);
2654 } else {
2655 self.root_loops.push(loop_id);
2656 }
2657 loop_id
2658 }
2659
2660 pub fn node_loop(&self, node_id: GraphNodeId) -> Option<GraphLoopId> {
2662 self.node_loops.get(node_id).copied()
2663 }
2664
2665 pub fn subgraph_loop(&self, subgraph_id: GraphSubgraphId) -> Option<GraphLoopId> {
2667 let &node_id = self.subgraph(subgraph_id).first().unwrap();
2668 let out = self.node_loop(node_id);
2669 debug_assert!(
2670 self.subgraph(subgraph_id)
2671 .iter()
2672 .all(|&node_id| self.node_loop(node_id) == out),
2673 "Subgraph nodes should all have the same loop context."
2674 );
2675 out
2676 }
2677
2678 pub fn loop_parent(&self, loop_id: GraphLoopId) -> Option<GraphLoopId> {
2680 self.loop_parent.get(loop_id).copied()
2681 }
2682
2683 pub fn loop_children(&self, loop_id: GraphLoopId) -> &Vec<GraphLoopId> {
2685 self.loop_children.get(loop_id).unwrap()
2686 }
2687
2688 pub fn root_loops(&self) -> &[GraphLoopId] {
2690 &self.root_loops
2691 }
2692}
2693
2694#[derive(Clone, Debug, Default)]
2696#[cfg_attr(feature = "clap-derive", derive(clap::Args))]
2697pub struct WriteConfig {
2698 #[cfg_attr(feature = "clap-derive", arg(long))]
2700 pub no_subgraphs: bool,
2701 #[cfg_attr(feature = "clap-derive", arg(long))]
2703 pub no_varnames: bool,
2704 #[cfg_attr(feature = "clap-derive", arg(long))]
2706 pub no_pull_push: bool,
2707 #[cfg_attr(feature = "clap-derive", arg(long))]
2709 pub no_handoffs: bool,
2710 #[cfg_attr(feature = "clap-derive", arg(long))]
2712 pub no_references: bool,
2713 #[cfg_attr(feature = "clap-derive", arg(long))]
2715 pub no_loops: bool,
2716
2717 #[cfg_attr(feature = "clap-derive", arg(long))]
2719 pub op_short_text: bool,
2720 #[cfg_attr(feature = "clap-derive", arg(long))]
2722 pub op_text_no_imports: bool,
2723}
2724
2725#[derive(Copy, Clone, Debug)]
2727#[cfg_attr(feature = "clap-derive", derive(clap::Parser, clap::ValueEnum))]
2728pub enum WriteGraphType {
2729 Mermaid,
2731 Dot,
2733}
2734
2735fn into_group_map<K, V>(iter: impl IntoIterator<Item = (K, V)>) -> BTreeMap<K, Vec<V>>
2737where
2738 K: Ord,
2739{
2740 let mut out: BTreeMap<_, Vec<_>> = BTreeMap::new();
2741 for (k, v) in iter {
2742 out.entry(k).or_default().push(v);
2743 }
2744 out
2745}