Skip to main content

hydro_lang/compile/ir/
mod.rs

1use core::panic;
2use std::cell::{Cell, RefCell};
3use std::collections::HashMap;
4#[cfg(feature = "build")]
5use std::collections::HashSet;
6use std::fmt::{Debug, Display};
7use std::hash::{Hash, Hasher};
8use std::ops::Deref;
9use std::rc::Rc;
10
11#[cfg(feature = "build")]
12use dfir_lang::graph::FlatGraphBuilder;
13#[cfg(feature = "build")]
14use proc_macro2::Span;
15use proc_macro2::TokenStream;
16use quote::ToTokens;
17#[cfg(feature = "build")]
18use quote::quote;
19#[cfg(feature = "build")]
20use slotmap::{SecondaryMap, SparseSecondaryMap};
21#[cfg(feature = "build")]
22use syn::parse_quote;
23
24#[cfg(feature = "build")]
25use crate::compile::builder::ClockId;
26#[cfg(feature = "build")]
27use crate::compile::builder::StmtId;
28use crate::compile::builder::{CycleId, ExternalPortId};
29#[cfg(feature = "build")]
30use crate::compile::deploy_provider::{Deploy, Node, RegisterPort};
31#[cfg(feature = "build")]
32use crate::handoff_ref::handoff_ref_ident;
33use crate::location::dynamic::{ClusterConsistency, LocationId};
34use crate::location::{LocationKey, NetworkHint};
35
36pub mod backtrace;
37use backtrace::Backtrace;
38
39/// A closure expression bundled with any singleton references it captures.
40///
41/// When a `q!()` closure captures a `SingletonRef`, the reference is recorded here
42/// alongside the closure's expression. This allows per-closure tracking of singleton
43/// captures, which is important for nodes with multiple closures (e.g. Fold has `init` and `acc`).
44pub struct ClosureExpr {
45    pub(crate) expr: DebugExpr,
46    /// Each entry is `(HydroNode::Reference, is_mut: bool)`.
47    /// The index in the Vec determines the ident name via [`handoff_ref_ident`].
48    /// The `access_counter` was assigned at staging time in code order.
49    pub(crate) singleton_refs: Vec<(HydroNode, bool)>,
50}
51
52impl Clone for ClosureExpr {
53    fn clone(&self) -> Self {
54        Self {
55            expr: self.expr.clone(),
56            singleton_refs: self
57                .singleton_refs
58                .iter()
59                .map(|(node, is_mut)| {
60                    let HydroNode::Reference {
61                        inner,
62                        kind,
63                        access_counter,
64                        metadata,
65                    } = node
66                    else {
67                        panic!("singleton_refs should only contain HydroNode::Reference");
68                    };
69                    (
70                        HydroNode::Reference {
71                            inner: SharedNode(Rc::clone(&inner.0)),
72                            kind: *kind,
73                            access_counter: access_counter.freeze(),
74                            metadata: metadata.clone(),
75                        },
76                        *is_mut,
77                    )
78                })
79                .collect(),
80        }
81    }
82}
83
84impl Hash for ClosureExpr {
85    fn hash<H: Hasher>(&self, state: &mut H) {
86        self.expr.hash(state);
87        // singleton_refs are structural children (like HydroIrMetadata), not
88        // identity-defining. Two closures with the same expr but different
89        // captured refs are the same closure text — the refs only affect codegen.
90    }
91}
92
93impl serde::Serialize for ClosureExpr {
94    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
95        use serde::ser::SerializeStruct;
96        let mut s = serializer.serialize_struct("ClosureExpr", 2)?;
97        s.serialize_field("expr", &self.expr)?;
98        s.serialize_field(
99            "singleton_refs",
100            &SerializableSingletonRefs(&self.singleton_refs),
101        )?;
102        s.end()
103    }
104}
105
106struct SerializableSingletonRefs<'a>(&'a [(HydroNode, bool)]);
107
108impl serde::Serialize for SerializableSingletonRefs<'_> {
109    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
110        use serde::ser::SerializeSeq;
111        let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
112        for (node, is_mut) in self.0.iter() {
113            seq.serialize_element(&(node, is_mut))?;
114        }
115        seq.end()
116    }
117}
118
119impl Debug for ClosureExpr {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        Debug::fmt(&self.expr, f)
122    }
123}
124
125impl Display for ClosureExpr {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        Display::fmt(&self.expr, f)
128    }
129}
130
131impl From<syn::Expr> for ClosureExpr {
132    fn from(expr: syn::Expr) -> Self {
133        Self {
134            expr: DebugExpr(Box::new(expr)),
135            singleton_refs: Vec::new(),
136        }
137    }
138}
139
140impl From<DebugExpr> for ClosureExpr {
141    fn from(expr: DebugExpr) -> Self {
142        Self {
143            expr,
144            singleton_refs: Vec::new(),
145        }
146    }
147}
148
149impl ClosureExpr {
150    pub fn new(expr: DebugExpr, singleton_refs: Vec<(HydroNode, bool)>) -> Self {
151        Self {
152            expr,
153            singleton_refs,
154        }
155    }
156
157    pub fn has_mut_ref(&self) -> bool {
158        self.singleton_refs.iter().any(|(_, is_mut)| *is_mut)
159    }
160
161    pub fn deep_clone(&self, seen_tees: &mut SeenSharedNodes) -> Self {
162        Self {
163            expr: self.expr.clone(),
164            singleton_refs: self
165                .singleton_refs
166                .iter()
167                .map(|(node, is_mut)| (node.deep_clone(seen_tees), *is_mut))
168                .collect(),
169        }
170    }
171
172    pub fn transform_children(
173        &mut self,
174        transform: &mut impl FnMut(&mut HydroNode, &mut SeenSharedNodes),
175        seen_tees: &mut SeenSharedNodes,
176    ) {
177        for (ref_node, _is_mut) in self.singleton_refs.iter_mut() {
178            transform(ref_node, seen_tees);
179        }
180    }
181
182    /// Pop singleton ref idents from the stack and rewrite the closure's token stream,
183    /// replacing local singleton ref idents with `#{N} dfir_ident` or `#{N} mut dfir_ident` references.
184    #[cfg(feature = "build")]
185    pub fn emit_tokens(&self, ident_stack: &mut Vec<syn::Ident>) -> TokenStream {
186        if self.singleton_refs.is_empty() {
187            self.expr.0.to_token_stream()
188        } else {
189            assert!(
190                ident_stack.len() >= self.singleton_refs.len(),
191                "ident_stack has {} entries but expected at least {} for singleton_refs",
192                ident_stack.len(),
193                self.singleton_refs.len()
194            );
195            let ref_idents = ident_stack.drain(ident_stack.len() - self.singleton_refs.len()..);
196
197            let mut let_bindings = Vec::new();
198            for ((i, (ref_node, is_mut)), ref_ident) in
199                self.singleton_refs.iter().enumerate().zip(ref_idents)
200            {
201                let HydroNode::Reference { access_counter, .. } = ref_node else {
202                    panic!("ClosureExpression expected references to `HydroNode::Reference`");
203                };
204                let group = access_counter.frozen_group();
205                // TODO(mingwei): proper spanning?
206                let local_ident = handoff_ref_ident(i);
207                let hash = proc_macro2::Punct::new('#', proc_macro2::Spacing::Alone);
208                let group_lit = proc_macro2::Literal::u32_unsuffixed(group);
209                let mut_token = is_mut.then(|| quote!(mut));
210                let binding = quote! {
211                    let #local_ident = #hash {#group_lit} #mut_token #ref_ident;
212                };
213                let_bindings.push(binding);
214            }
215
216            let expr = &self.expr.0;
217            quote! {
218                {
219                    #( #let_bindings )*
220                    #expr
221                }
222            }
223        }
224    }
225}
226
227/// Wrapper that displays only the tokens of a parsed expr.
228///
229/// Boxes `syn::Type` which is ~240 bytes.
230#[derive(Clone, Hash)]
231pub struct DebugExpr(pub Box<syn::Expr>);
232
233impl serde::Serialize for DebugExpr {
234    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
235        serializer.serialize_str(&self.to_string())
236    }
237}
238
239impl From<syn::Expr> for DebugExpr {
240    fn from(expr: syn::Expr) -> Self {
241        Self(Box::new(expr))
242    }
243}
244
245impl Deref for DebugExpr {
246    type Target = syn::Expr;
247
248    fn deref(&self) -> &Self::Target {
249        &self.0
250    }
251}
252
253impl ToTokens for DebugExpr {
254    fn to_tokens(&self, tokens: &mut TokenStream) {
255        self.0.to_tokens(tokens);
256    }
257}
258
259impl Debug for DebugExpr {
260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261        write!(f, "{}", self.0.to_token_stream())
262    }
263}
264
265impl Display for DebugExpr {
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        let original = self.0.as_ref().clone();
268        let simplified = simplify_q_macro(original);
269
270        // For now, just use quote formatting without trying to parse as a statement
271        // This avoids the syn::parse_quote! issues entirely
272        write!(f, "q!({})", quote::quote!(#simplified))
273    }
274}
275
276/// Simplify expanded q! macro calls back to q!(...) syntax for better readability
277fn simplify_q_macro(expr: syn::Expr) -> syn::Expr {
278    if let syn::Expr::Call(ref call) = expr && let syn::Expr::Path(path_expr) = call.func.as_ref()
279        // Look for calls to stageleft::runtime_support::fn*
280        && is_stageleft_runtime_support_call(&path_expr.path)
281        && let syn::Expr::Block(b) = &call.args[0]
282        && b.block.stmts.len() == 3
283        && let Some(syn::Stmt::Expr(e, _)) = b.block.stmts.get(2)
284    // skip the first two, which are imports
285    {
286        let mut e = e.clone();
287        while let syn::Expr::Block(ref mut block) = e
288            && block.block.stmts.len() == 1
289            && let syn::Stmt::Expr(inner_e, _) = block.block.stmts.remove(0)
290        {
291            e = inner_e;
292        }
293
294        e
295    } else {
296        expr
297    }
298}
299
300fn is_stageleft_runtime_support_call(path: &syn::Path) -> bool {
301    // Check if this is a call to stageleft::runtime_support::fn*
302    if let Some(last_segment) = path.segments.last() {
303        let fn_name = last_segment.ident.to_string();
304        path.segments.len() > 2
305            && path.segments[0].ident == "stageleft"
306            && path.segments[1].ident == "runtime_support"
307            && fn_name.contains("_type_hint")
308    } else {
309        false
310    }
311}
312
313/// Debug displays the type's tokens.
314///
315/// Boxes `syn::Type` which is ~320 bytes.
316#[derive(Clone, PartialEq, Eq, Hash)]
317pub struct DebugType(pub Box<syn::Type>);
318
319impl From<syn::Type> for DebugType {
320    fn from(t: syn::Type) -> Self {
321        Self(Box::new(t))
322    }
323}
324
325impl Deref for DebugType {
326    type Target = syn::Type;
327
328    fn deref(&self) -> &Self::Target {
329        &self.0
330    }
331}
332
333impl ToTokens for DebugType {
334    fn to_tokens(&self, tokens: &mut TokenStream) {
335        self.0.to_tokens(tokens);
336    }
337}
338
339impl Debug for DebugType {
340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        write!(f, "{}", self.0.to_token_stream())
342    }
343}
344
345impl serde::Serialize for DebugType {
346    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
347        serializer.serialize_str(&format!("{}", self.0.to_token_stream()))
348    }
349}
350
351fn serialize_backtrace_as_span<S: serde::Serializer>(
352    backtrace: &Backtrace,
353    serializer: S,
354) -> Result<S::Ok, S::Error> {
355    match backtrace.format_span() {
356        Some(span) => serializer.serialize_some(&span),
357        None => serializer.serialize_none(),
358    }
359}
360
361fn serialize_ident<S: serde::Serializer>(
362    ident: &syn::Ident,
363    serializer: S,
364) -> Result<S::Ok, S::Error> {
365    serializer.serialize_str(&ident.to_string())
366}
367
368pub enum DebugInstantiate {
369    Building,
370    Finalized(Box<DebugInstantiateFinalized>),
371}
372
373impl serde::Serialize for DebugInstantiate {
374    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
375        match self {
376            DebugInstantiate::Building => {
377                serializer.serialize_unit_variant("DebugInstantiate", 0, "Building")
378            }
379            DebugInstantiate::Finalized(_) => {
380                panic!(
381                    "cannot serialize DebugInstantiate::Finalized: contains non-serializable runtime state (closures)"
382                )
383            }
384        }
385    }
386}
387
388#[cfg_attr(
389    not(feature = "build"),
390    expect(
391        dead_code,
392        reason = "sink, source unused without `feature = \"build\"`."
393    )
394)]
395pub struct DebugInstantiateFinalized {
396    sink: syn::Expr,
397    source: syn::Expr,
398    connect_fn: Option<Box<dyn FnOnce()>>,
399}
400
401impl From<DebugInstantiateFinalized> for DebugInstantiate {
402    fn from(f: DebugInstantiateFinalized) -> Self {
403        Self::Finalized(Box::new(f))
404    }
405}
406
407impl Debug for DebugInstantiate {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        write!(f, "<network instantiate>")
410    }
411}
412
413impl Hash for DebugInstantiate {
414    fn hash<H: Hasher>(&self, _state: &mut H) {
415        // Do nothing
416    }
417}
418
419impl Clone for DebugInstantiate {
420    fn clone(&self) -> Self {
421        match self {
422            DebugInstantiate::Building => DebugInstantiate::Building,
423            DebugInstantiate::Finalized(_) => {
424                panic!("DebugInstantiate::Finalized should not be cloned")
425            }
426        }
427    }
428}
429
430/// Tracks the instantiation state of a `ClusterMembers` source.
431///
432/// During `compile_network`, the first `ClusterMembers` node for a given
433/// `(at_location, target_cluster)` pair is promoted to [`Self::Stream`] and
434/// receives the expression returned by `Deploy::cluster_membership_stream`.
435/// All subsequent nodes for the same pair are set to [`Self::Tee`] so that
436/// during code-gen they simply reference the tee output of the first node
437/// instead of creating a redundant `source_stream`.
438#[derive(Debug, Hash, Clone, serde::Serialize)]
439pub enum ClusterMembersState {
440    /// Not yet instantiated.
441    Uninit,
442    /// The primary instance: holds the stream expression and will emit
443    /// `source_stream(expr) -> tee()` during code-gen.
444    Stream(DebugExpr),
445    /// A secondary instance that references the tee output of the primary.
446    /// Stores `(at_location_root, target_cluster_location)` so that `emit_core`
447    /// can derive the deterministic tee ident without extra state.
448    Tee(LocationId, LocationId),
449}
450
451/// A source in a Hydro graph, where data enters the graph.
452#[derive(Debug, Hash, Clone, serde::Serialize)]
453pub enum HydroSource {
454    Stream(DebugExpr),
455    ExternalNetwork(),
456    Iter(DebugExpr),
457    Spin(),
458    ClusterMembers(LocationId, ClusterMembersState),
459    Embedded(#[serde(serialize_with = "serialize_ident")] syn::Ident),
460    EmbeddedSingleton(#[serde(serialize_with = "serialize_ident")] syn::Ident),
461}
462
463#[cfg(feature = "build")]
464/// A trait that abstracts over elements of DFIR code-gen that differ between production deployment
465/// and simulations.
466///
467/// In particular, this lets the simulator fuse together all locations into one DFIR graph, spit
468/// out separate graphs for each tick, and emit hooks for controlling non-deterministic operators.
469pub trait DfirBuilder {
470    /// Whether the representation of singletons should include intermediate states.
471    fn singleton_intermediates(&self) -> bool;
472
473    /// Adds the DFIR statements to the graph for the given location.
474    ///
475    /// The location determines which DFIR graph the statements are placed in (for production,
476    /// the graph of the location's root; for simulation, either the fused async graph or the
477    /// tick's separate graph). In the future (#2902), production codegen will also use the
478    /// location to place tick-located statements inside the tick's `loop { ... }` context.
479    fn add_dfir_at(
480        &mut self,
481        location: &LocationId,
482        dfir: dfir_lang::parse::DfirCode,
483        operator_tag: Option<&str>,
484    );
485
486    /// The DFIR persistence lifetime for operator state scoped to a single tick, for an operator
487    /// at `op_location`.
488    ///
489    /// Returns `'tick`. In the future (#2902), production codegen will emit tick regions as DFIR
490    /// `loop { ... }` blocks, where this must instead be `'none` when `op_location` is a tick.
491    fn tick_state_lifetime(&self, _op_location: &LocationId) -> TokenStream {
492        quote!('tick)
493    }
494
495    /// The DFIR persistence lifetime for operator state that accumulates across ticks, for an
496    /// operator at `op_location`.
497    ///
498    /// Returns `'static`. In the future (#2902), production codegen will emit tick regions as
499    /// DFIR `loop { ... }` blocks, where this must instead be `'loop` when `op_location` is a
500    /// tick.
501    fn cross_tick_state_lifetime(&self, _op_location: &LocationId) -> TokenStream {
502        quote!('static)
503    }
504
505    #[expect(clippy::too_many_arguments, reason = "TODO")]
506    fn batch(
507        &mut self,
508        in_ident: syn::Ident,
509        in_location: &LocationId,
510        in_kind: &CollectionKind,
511        out_ident: &syn::Ident,
512        out_location: &LocationId,
513        op_meta: &HydroIrOpMetadata,
514        fold_hooked_idents: &HashSet<String>,
515    );
516    fn yield_from_tick(
517        &mut self,
518        in_ident: syn::Ident,
519        in_location: &LocationId,
520        in_kind: &CollectionKind,
521        out_ident: &syn::Ident,
522        out_location: &LocationId,
523    );
524
525    fn begin_atomic(
526        &mut self,
527        in_ident: syn::Ident,
528        in_location: &LocationId,
529        in_kind: &CollectionKind,
530        out_ident: &syn::Ident,
531        out_location: &LocationId,
532        op_meta: &HydroIrOpMetadata,
533    );
534    fn end_atomic(
535        &mut self,
536        in_ident: syn::Ident,
537        in_location: &LocationId,
538        in_kind: &CollectionKind,
539        out_ident: &syn::Ident,
540    );
541
542    #[expect(clippy::too_many_arguments, reason = "TODO // internal")]
543    fn observe_nondet(
544        &mut self,
545        trusted: bool,
546        location: &LocationId,
547        in_ident: syn::Ident,
548        in_kind: &CollectionKind,
549        out_ident: &syn::Ident,
550        out_kind: &CollectionKind,
551        op_meta: &HydroIrOpMetadata,
552    );
553
554    #[expect(clippy::too_many_arguments, reason = "TODO")]
555    fn merge_ordered(
556        &mut self,
557        location: &LocationId,
558        first_ident: syn::Ident,
559        second_ident: syn::Ident,
560        out_ident: &syn::Ident,
561        in_kind: &CollectionKind,
562        op_meta: &HydroIrOpMetadata,
563        operator_tag: Option<&str>,
564    );
565
566    #[expect(clippy::too_many_arguments, reason = "TODO")]
567    fn create_network(
568        &mut self,
569        from: &LocationId,
570        to: &LocationId,
571        input_ident: syn::Ident,
572        out_ident: &syn::Ident,
573        serialize: Option<&DebugExpr>,
574        sink: syn::Expr,
575        source: syn::Expr,
576        deserialize: Option<&DebugExpr>,
577        external_element_type: Option<&syn::Type>,
578        tag_id: StmtId,
579        networking_info: &crate::networking::NetworkingInfo,
580    );
581
582    fn create_external_source(
583        &mut self,
584        on: &LocationId,
585        source_expr: syn::Expr,
586        out_ident: &syn::Ident,
587        deserialize: Option<&DebugExpr>,
588        tag_id: StmtId,
589    );
590
591    fn create_external_output(
592        &mut self,
593        on: &LocationId,
594        sink_expr: syn::Expr,
595        input_ident: &syn::Ident,
596        serialize: Option<&DebugExpr>,
597        tag_id: StmtId,
598    );
599
600    /// Optionally emit a fold hook that buffers and permutes inputs before the fold.
601    /// Returns the new input ident to use for the fold if a hook was emitted.
602    fn emit_fold_hook(
603        &mut self,
604        location: &LocationId,
605        in_ident: &syn::Ident,
606        in_kind: &CollectionKind,
607        op_meta: &HydroIrOpMetadata,
608    ) -> Option<syn::Ident>;
609
610    /// Inserts necessary code to validate a manual assertion that at this point the
611    /// input live collection is consistent. In production, this is a no-op, but in simulation
612    /// this will (not yet implemented) inject assertions that validate consistency.
613    fn assert_is_consistent(
614        &mut self,
615        trusted: bool,
616        location: &LocationId,
617        in_ident: syn::Ident,
618        out_ident: &syn::Ident,
619    );
620
621    /// Observes non-determinism introduced by a mut closure operating on a non-strict
622    /// (unordered / at-least-once) input. In production this is identity; in simulation
623    /// it delegates to `observe_nondet` with the strict output kind.
624    fn observe_for_mut(
625        &mut self,
626        location: &LocationId,
627        in_ident: syn::Ident,
628        in_kind: &CollectionKind,
629        out_ident: &syn::Ident,
630        op_meta: &HydroIrOpMetadata,
631    );
632
633    fn create_versioned_network_fork(
634        &mut self,
635        channel_id: u32,
636        dest: &LocationId,
637        senders: Vec<(LocationId, syn::Ident, Option<DebugExpr>)>,
638        external_element_type: Option<&syn::Type>,
639        tag_id: StmtId,
640    );
641
642    #[expect(clippy::too_many_arguments, reason = "networking codegen")]
643    fn create_versioned_network(
644        &mut self,
645        channel_id: u32,
646        source: &LocationId,
647        dest: &LocationId,
648        out_ident: &syn::Ident,
649        deserialize: Option<&DebugExpr>,
650        external_element_type: Option<&syn::Type>,
651        tag_id: StmtId,
652    );
653}
654
655/// The production (deployment) DFIR builder: emits one DFIR graph per root location
656/// (process/cluster).
657///
658/// Tick and atomic locations are collapsed onto their root location's graph. In the future
659/// (#2902), this builder will additionally emit each (unified) tick as a root-level
660/// `loop {{ ... }}` context within its root location's graph.
661#[cfg(feature = "build")]
662#[derive(Default)]
663pub struct ProdDfirBuilder {
664    /// The DFIR graph builder for each root location.
665    pub graphs: SecondaryMap<LocationKey, FlatGraphBuilder>,
666}
667
668#[cfg(feature = "build")]
669impl ProdDfirBuilder {
670    /// Gets the DFIR builder for the given location's root, creating it if necessary.
671    fn graph_mut(&mut self, location: &LocationId) -> &mut FlatGraphBuilder {
672        self.graphs
673            .entry(location.root().key())
674            .expect("location was removed")
675            .or_default()
676    }
677}
678
679#[cfg(feature = "build")]
680impl DfirBuilder for ProdDfirBuilder {
681    fn singleton_intermediates(&self) -> bool {
682        false
683    }
684
685    fn add_dfir_at(
686        &mut self,
687        location: &LocationId,
688        dfir: dfir_lang::parse::DfirCode,
689        operator_tag: Option<&str>,
690    ) {
691        self.graph_mut(location).add_dfir(dfir, None, operator_tag);
692    }
693
694    fn batch(
695        &mut self,
696        in_ident: syn::Ident,
697        in_location: &LocationId,
698        in_kind: &CollectionKind,
699        out_ident: &syn::Ident,
700        _out_location: &LocationId,
701        _op_meta: &HydroIrOpMetadata,
702        _fold_hooked_idents: &HashSet<String>,
703    ) {
704        let builder = self.graph_mut(in_location.root());
705        if in_kind.is_bounded()
706            && matches!(
707                in_kind,
708                CollectionKind::Singleton { .. }
709                    | CollectionKind::Optional { .. }
710                    | CollectionKind::KeyedSingleton { .. }
711            )
712        {
713            assert!(in_location.is_top_level());
714            builder.add_dfir(
715                parse_quote! {
716                    #out_ident = #in_ident -> persist::<'static>();
717                },
718                None,
719                None,
720            );
721        } else {
722            builder.add_dfir(
723                parse_quote! {
724                    #out_ident = #in_ident;
725                },
726                None,
727                None,
728            );
729        }
730    }
731
732    fn yield_from_tick(
733        &mut self,
734        in_ident: syn::Ident,
735        in_location: &LocationId,
736        _in_kind: &CollectionKind,
737        out_ident: &syn::Ident,
738        _out_location: &LocationId,
739    ) {
740        let builder = self.graph_mut(in_location.root());
741        builder.add_dfir(
742            parse_quote! {
743                #out_ident = #in_ident;
744            },
745            None,
746            None,
747        );
748    }
749
750    fn begin_atomic(
751        &mut self,
752        in_ident: syn::Ident,
753        in_location: &LocationId,
754        _in_kind: &CollectionKind,
755        out_ident: &syn::Ident,
756        _out_location: &LocationId,
757        _op_meta: &HydroIrOpMetadata,
758    ) {
759        let builder = self.graph_mut(in_location.root());
760        builder.add_dfir(
761            parse_quote! {
762                #out_ident = #in_ident;
763            },
764            None,
765            None,
766        );
767    }
768
769    fn end_atomic(
770        &mut self,
771        in_ident: syn::Ident,
772        in_location: &LocationId,
773        _in_kind: &CollectionKind,
774        out_ident: &syn::Ident,
775    ) {
776        let builder = self.graph_mut(in_location.root());
777        builder.add_dfir(
778            parse_quote! {
779                #out_ident = #in_ident;
780            },
781            None,
782            None,
783        );
784    }
785
786    fn observe_nondet(
787        &mut self,
788        _trusted: bool,
789        location: &LocationId,
790        in_ident: syn::Ident,
791        _in_kind: &CollectionKind,
792        out_ident: &syn::Ident,
793        _out_kind: &CollectionKind,
794        _op_meta: &HydroIrOpMetadata,
795    ) {
796        let builder = self.graph_mut(location);
797        builder.add_dfir(
798            parse_quote! {
799                #out_ident = #in_ident;
800            },
801            None,
802            None,
803        );
804    }
805
806    fn merge_ordered(
807        &mut self,
808        location: &LocationId,
809        first_ident: syn::Ident,
810        second_ident: syn::Ident,
811        out_ident: &syn::Ident,
812        _in_kind: &CollectionKind,
813        _op_meta: &HydroIrOpMetadata,
814        operator_tag: Option<&str>,
815    ) {
816        let builder = self.graph_mut(location);
817        builder.add_dfir(
818            parse_quote! {
819                #out_ident = union();
820                #first_ident -> [0]#out_ident;
821                #second_ident -> [1]#out_ident;
822            },
823            None,
824            operator_tag,
825        );
826    }
827
828    fn create_network(
829        &mut self,
830        from: &LocationId,
831        to: &LocationId,
832        input_ident: syn::Ident,
833        out_ident: &syn::Ident,
834        serialize: Option<&DebugExpr>,
835        sink: syn::Expr,
836        source: syn::Expr,
837        deserialize: Option<&DebugExpr>,
838        _external_element_type: Option<&syn::Type>,
839        tag_id: StmtId,
840        _networking_info: &crate::networking::NetworkingInfo,
841    ) {
842        let sender_builder = self.graph_mut(from);
843        if let Some(serialize_pipeline) = serialize {
844            sender_builder.add_dfir(
845                parse_quote! {
846                    #input_ident -> map(#serialize_pipeline) -> dest_sink(#sink);
847                },
848                None,
849                // operator tag separates send and receive, which otherwise have the same next_stmt_id
850                Some(&format!("send{}", tag_id)),
851            );
852        } else {
853            sender_builder.add_dfir(
854                parse_quote! {
855                    #input_ident -> dest_sink(#sink);
856                },
857                None,
858                Some(&format!("send{}", tag_id)),
859            );
860        }
861
862        let receiver_builder = self.graph_mut(to);
863        if let Some(deserialize_pipeline) = deserialize {
864            receiver_builder.add_dfir(
865                parse_quote! {
866                    #out_ident = source_stream(#source) -> map(#deserialize_pipeline);
867                },
868                None,
869                Some(&format!("recv{}", tag_id)),
870            );
871        } else {
872            receiver_builder.add_dfir(
873                parse_quote! {
874                    #out_ident = source_stream(#source);
875                },
876                None,
877                Some(&format!("recv{}", tag_id)),
878            );
879        }
880    }
881
882    fn create_external_source(
883        &mut self,
884        on: &LocationId,
885        source_expr: syn::Expr,
886        out_ident: &syn::Ident,
887        deserialize: Option<&DebugExpr>,
888        tag_id: StmtId,
889    ) {
890        let receiver_builder = self.graph_mut(on);
891        if let Some(deserialize_pipeline) = deserialize {
892            receiver_builder.add_dfir(
893                parse_quote! {
894                    #out_ident = source_stream(#source_expr) -> map(#deserialize_pipeline);
895                },
896                None,
897                Some(&format!("recv{}", tag_id)),
898            );
899        } else {
900            receiver_builder.add_dfir(
901                parse_quote! {
902                    #out_ident = source_stream(#source_expr);
903                },
904                None,
905                Some(&format!("recv{}", tag_id)),
906            );
907        }
908    }
909
910    fn create_external_output(
911        &mut self,
912        on: &LocationId,
913        sink_expr: syn::Expr,
914        input_ident: &syn::Ident,
915        serialize: Option<&DebugExpr>,
916        tag_id: StmtId,
917    ) {
918        let sender_builder = self.graph_mut(on);
919        if let Some(serialize_fn) = serialize {
920            sender_builder.add_dfir(
921                parse_quote! {
922                    #input_ident -> map(#serialize_fn) -> dest_sink(#sink_expr);
923                },
924                None,
925                // operator tag separates send and receive, which otherwise have the same next_stmt_id
926                Some(&format!("send{}", tag_id)),
927            );
928        } else {
929            sender_builder.add_dfir(
930                parse_quote! {
931                    #input_ident -> dest_sink(#sink_expr);
932                },
933                None,
934                Some(&format!("send{}", tag_id)),
935            );
936        }
937    }
938
939    fn emit_fold_hook(
940        &mut self,
941        _location: &LocationId,
942        _in_ident: &syn::Ident,
943        _in_kind: &CollectionKind,
944        _op_meta: &HydroIrOpMetadata,
945    ) -> Option<syn::Ident> {
946        None
947    }
948
949    fn assert_is_consistent(
950        &mut self,
951        _trusted: bool,
952        location: &LocationId,
953        in_ident: syn::Ident,
954        out_ident: &syn::Ident,
955    ) {
956        let builder = self.graph_mut(location);
957        builder.add_dfir(
958            parse_quote! {
959                #out_ident = #in_ident;
960            },
961            None,
962            None,
963        );
964    }
965
966    fn observe_for_mut(
967        &mut self,
968        location: &LocationId,
969        in_ident: syn::Ident,
970        _in_kind: &CollectionKind,
971        out_ident: &syn::Ident,
972        _op_meta: &HydroIrOpMetadata,
973    ) {
974        let builder = self.graph_mut(location);
975        builder.add_dfir(
976            parse_quote! {
977                #out_ident = #in_ident;
978            },
979            None,
980            None,
981        );
982    }
983
984    fn create_versioned_network_fork(
985        &mut self,
986        _channel_id: u32,
987        _dest: &LocationId,
988        _senders: Vec<(LocationId, syn::Ident, Option<DebugExpr>)>,
989        _external_element_type: Option<&syn::Type>,
990        _tag_id: StmtId,
991    ) {
992        unreachable!(
993            "HydroNode::VersionedNetworkFork is only produced by the multi-version simulator merge \
994             pass and cannot be emitted by the non-simulation builder"
995        );
996    }
997
998    fn create_versioned_network(
999        &mut self,
1000        _channel_id: u32,
1001        _source: &LocationId,
1002        _dest: &LocationId,
1003        _out_ident: &syn::Ident,
1004        _deserialize: Option<&DebugExpr>,
1005        _external_element_type: Option<&syn::Type>,
1006        _tag_id: StmtId,
1007    ) {
1008        unreachable!(
1009            "HydroNode::VersionedNetwork is only produced by the multi-version simulator merge \
1010             pass and cannot be emitted by the non-simulation builder"
1011        );
1012    }
1013}
1014
1015#[cfg(feature = "build")]
1016pub enum BuildersOrCallback<'a, L, N>
1017where
1018    L: FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
1019    N: FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
1020{
1021    Builders(&'a mut dyn DfirBuilder),
1022    Callback(L, N),
1023}
1024
1025/// An root in a Hydro graph, which is an pipeline that doesn't emit
1026/// any downstream values. Traversals over the dataflow graph and
1027/// generating DFIR IR start from roots.
1028#[derive(Debug, Hash, serde::Serialize)]
1029pub enum HydroRoot {
1030    ForEach {
1031        f: ClosureExpr,
1032        input: Box<HydroNode>,
1033        op_metadata: HydroIrOpMetadata,
1034    },
1035    SendExternal {
1036        to_external_key: LocationKey,
1037        to_port_id: ExternalPortId,
1038        to_many: bool,
1039        unpaired: bool,
1040        serialize_fn: Option<DebugExpr>,
1041        instantiate_fn: DebugInstantiate,
1042        input: Box<HydroNode>,
1043        op_metadata: HydroIrOpMetadata,
1044    },
1045    DestSink {
1046        sink: DebugExpr,
1047        input: Box<HydroNode>,
1048        op_metadata: HydroIrOpMetadata,
1049    },
1050    CycleSink {
1051        cycle_id: CycleId,
1052        input: Box<HydroNode>,
1053        op_metadata: HydroIrOpMetadata,
1054    },
1055    EmbeddedOutput {
1056        #[serde(serialize_with = "serialize_ident")]
1057        ident: syn::Ident,
1058        input: Box<HydroNode>,
1059        op_metadata: HydroIrOpMetadata,
1060    },
1061    Null {
1062        input: Box<HydroNode>,
1063        op_metadata: HydroIrOpMetadata,
1064    },
1065}
1066
1067impl HydroRoot {
1068    #[cfg(feature = "build")]
1069    #[expect(clippy::too_many_arguments, reason = "TODO(internal)")]
1070    pub fn compile_network<'a, D>(
1071        &mut self,
1072        extra_stmts: &mut SparseSecondaryMap<LocationKey, Vec<syn::Stmt>>,
1073        seen_tees: &mut SeenSharedNodes,
1074        seen_cluster_members: &mut HashSet<(LocationId, LocationKey)>,
1075        processes: &SparseSecondaryMap<LocationKey, D::Process>,
1076        clusters: &SparseSecondaryMap<LocationKey, D::Cluster>,
1077        externals: &SparseSecondaryMap<LocationKey, D::External>,
1078        env: &mut D::InstantiateEnv,
1079    ) where
1080        D: Deploy<'a>,
1081    {
1082        let refcell_extra_stmts = RefCell::new(extra_stmts);
1083        let refcell_env = RefCell::new(env);
1084        let refcell_seen_cluster_members = RefCell::new(seen_cluster_members);
1085        self.transform_bottom_up(
1086            &mut |l| {
1087                if let HydroRoot::SendExternal {
1088                    #[cfg(feature = "tokio")]
1089                    input,
1090                    #[cfg(feature = "tokio")]
1091                    to_external_key,
1092                    #[cfg(feature = "tokio")]
1093                    to_port_id,
1094                    #[cfg(feature = "tokio")]
1095                    to_many,
1096                    #[cfg(feature = "tokio")]
1097                    unpaired,
1098                    #[cfg(feature = "tokio")]
1099                    instantiate_fn,
1100                    ..
1101                } = l
1102                {
1103                    #[cfg(feature = "tokio")]
1104                    let ((sink_expr, source_expr), connect_fn) = match instantiate_fn {
1105                        DebugInstantiate::Building => {
1106                            let to_node = externals
1107                                .get(*to_external_key)
1108                                .unwrap_or_else(|| {
1109                                    panic!("A external used in the graph was not instantiated: {}", to_external_key)
1110                                })
1111                                .clone();
1112
1113                            match input.metadata().location_id.root() {
1114                                &LocationId::Process(process_key) => {
1115                                    if *to_many {
1116                                        (
1117                                            (
1118                                                D::e2o_many_sink(format!("{}_{}", *to_external_key, *to_port_id)),
1119                                                parse_quote!(DUMMY),
1120                                            ),
1121                                            Box::new(|| {}) as Box<dyn FnOnce()>,
1122                                        )
1123                                    } else {
1124                                        let from_node = processes
1125                                            .get(process_key)
1126                                            .unwrap_or_else(|| {
1127                                                panic!("A process used in the graph was not instantiated: {}", process_key)
1128                                            })
1129                                            .clone();
1130
1131                                        let sink_port = from_node.next_port();
1132                                        let source_port = to_node.next_port();
1133
1134                                        if *unpaired {
1135                                            use stageleft::quote_type;
1136                                            use tokio_util::codec::LengthDelimitedCodec;
1137
1138                                            to_node.register(*to_port_id, source_port.clone());
1139
1140                                            let _ = D::e2o_source(
1141                                                refcell_extra_stmts.borrow_mut().entry(process_key).expect("location was removed").or_default(),
1142                                                &to_node, &source_port,
1143                                                &from_node, &sink_port,
1144                                                &quote_type::<LengthDelimitedCodec>(),
1145                                                format!("{}_{}", *to_external_key, *to_port_id)
1146                                            );
1147                                        }
1148
1149                                        (
1150                                            (
1151                                                D::o2e_sink(
1152                                                    &from_node,
1153                                                    &sink_port,
1154                                                    &to_node,
1155                                                    &source_port,
1156                                                    format!("{}_{}", *to_external_key, *to_port_id)
1157                                                ),
1158                                                parse_quote!(DUMMY),
1159                                            ),
1160                                            if *unpaired {
1161                                                D::e2o_connect(
1162                                                    &to_node,
1163                                                    &source_port,
1164                                                    &from_node,
1165                                                    &sink_port,
1166                                                    *to_many,
1167                                                    NetworkHint::Auto,
1168                                                )
1169                                            } else {
1170                                                Box::new(|| {}) as Box<dyn FnOnce()>
1171                                            },
1172                                        )
1173                                    }
1174                                }
1175                                LocationId::Cluster(cluster_key) => {
1176                                    let from_node = clusters
1177                                        .get(*cluster_key)
1178                                        .unwrap_or_else(|| {
1179                                            panic!("A cluster used in the graph was not instantiated: {}", cluster_key)
1180                                        })
1181                                        .clone();
1182
1183                                    let sink_port = from_node.next_port();
1184                                    let source_port = to_node.next_port();
1185
1186                                    if *unpaired {
1187                                        to_node.register(*to_port_id, source_port.clone());
1188                                    }
1189
1190                                    (
1191                                        (
1192                                            D::m2e_sink(
1193                                                &from_node,
1194                                                &sink_port,
1195                                                &to_node,
1196                                                &source_port,
1197                                                format!("{}_{}", *to_external_key, *to_port_id)
1198                                            ),
1199                                            parse_quote!(DUMMY),
1200                                        ),
1201                                        Box::new(|| {}) as Box<dyn FnOnce()>,
1202                                    )
1203                                }
1204                                _ => panic!()
1205                            }
1206                        },
1207
1208                        DebugInstantiate::Finalized(_) => panic!("network already finalized"),
1209                    };
1210
1211                    #[cfg(not(feature = "tokio"))]
1212                    {
1213                        panic!("Cannot instantiate external inputs without tokio");
1214                    };
1215
1216                    #[cfg(feature = "tokio")]
1217                    {
1218                        *instantiate_fn = DebugInstantiateFinalized {
1219                            sink: sink_expr,
1220                            source: source_expr,
1221                            connect_fn: Some(connect_fn),
1222                        }
1223                        .into();
1224                    };
1225                } else if let HydroRoot::EmbeddedOutput { ident, input, .. } = l {
1226                    let element_type = match &input.metadata().collection_kind {
1227                        CollectionKind::Stream { element_type, .. } => element_type.0.as_ref().clone(),
1228                        _ => panic!("Embedded output must have Stream collection kind"),
1229                    };
1230                    let location_key = match input.metadata().location_id.root() {
1231                        LocationId::Process(key) | LocationId::Cluster(key) => *key,
1232                        _ => panic!("Embedded output must be on a process or cluster"),
1233                    };
1234                    D::register_embedded_output(
1235                        &mut refcell_env.borrow_mut(),
1236                        location_key,
1237                        ident,
1238                        &element_type,
1239                    );
1240                }
1241            },
1242            &mut |n| {
1243                if let HydroNode::Network {
1244                    name,
1245                    networking_info,
1246                    input,
1247                    instantiate_fn,
1248                    serialize,
1249                    deserialize,
1250                    metadata,
1251                    ..
1252                } = n
1253                {
1254                    let external_types = match (
1255                        serialize.external_element_type(),
1256                        deserialize.external_element_type(),
1257                    ) {
1258                        (Some(input_type), Some(output_type)) => Some((input_type, output_type)),
1259                        _ => None,
1260                    };
1261                    let (sink_expr, source_expr, connect_fn) = match instantiate_fn {
1262                        DebugInstantiate::Building => instantiate_network::<D>(
1263                            &mut refcell_env.borrow_mut(),
1264                            input.metadata().location_id.root(),
1265                            metadata.location_id.root(),
1266                            processes,
1267                            clusters,
1268                            name.as_deref(),
1269                            networking_info,
1270                            external_types,
1271                        ),
1272
1273                        DebugInstantiate::Finalized(_) => panic!("network already finalized"),
1274                    };
1275
1276                    *instantiate_fn = DebugInstantiateFinalized {
1277                        sink: sink_expr,
1278                        source: source_expr,
1279                        connect_fn: Some(connect_fn),
1280                    }
1281                    .into();
1282                } else if let HydroNode::ExternalInput {
1283                    from_external_key,
1284                    from_port_id,
1285                    from_many,
1286                    codec_type,
1287                    port_hint,
1288                    instantiate_fn,
1289                    metadata,
1290                    ..
1291                } = n
1292                {
1293                    let ((sink_expr, source_expr), connect_fn) = match instantiate_fn {
1294                        DebugInstantiate::Building => {
1295                            let from_node = externals
1296                                .get(*from_external_key)
1297                                .unwrap_or_else(|| {
1298                                    panic!(
1299                                        "A external used in the graph was not instantiated: {}",
1300                                        from_external_key,
1301                                    )
1302                                })
1303                                .clone();
1304
1305                            match metadata.location_id.root() {
1306                                &LocationId::Process(process_key) => {
1307                                    let to_node = processes
1308                                        .get(process_key)
1309                                        .unwrap_or_else(|| {
1310                                            panic!("A process used in the graph was not instantiated: {}", process_key)
1311                                        })
1312                                        .clone();
1313
1314                                    let sink_port = from_node.next_port();
1315                                    let source_port = to_node.next_port();
1316
1317                                    from_node.register(*from_port_id, sink_port.clone());
1318
1319                                    (
1320                                        (
1321                                            parse_quote!(DUMMY),
1322                                            if *from_many {
1323                                                D::e2o_many_source(
1324                                                    refcell_extra_stmts.borrow_mut().entry(process_key).expect("location was removed").or_default(),
1325                                                    &to_node, &source_port,
1326                                                    codec_type.0.as_ref(),
1327                                                    format!("{}_{}", *from_external_key, *from_port_id)
1328                                                )
1329                                            } else {
1330                                                D::e2o_source(
1331                                                    refcell_extra_stmts.borrow_mut().entry(process_key).expect("location was removed").or_default(),
1332                                                    &from_node, &sink_port,
1333                                                    &to_node, &source_port,
1334                                                    codec_type.0.as_ref(),
1335                                                    format!("{}_{}", *from_external_key, *from_port_id)
1336                                                )
1337                                            },
1338                                        ),
1339                                        D::e2o_connect(&from_node, &sink_port, &to_node, &source_port, *from_many, *port_hint),
1340                                    )
1341                                }
1342                                LocationId::Cluster(cluster_key) => {
1343                                    let to_node = clusters
1344                                        .get(*cluster_key)
1345                                        .unwrap_or_else(|| {
1346                                            panic!("A cluster used in the graph was not instantiated: {}", cluster_key)
1347                                        })
1348                                        .clone();
1349
1350                                    let sink_port = from_node.next_port();
1351                                    let source_port = to_node.next_port();
1352
1353                                    from_node.register(*from_port_id, sink_port.clone());
1354
1355                                    (
1356                                        (
1357                                            parse_quote!(DUMMY),
1358                                            D::e2m_source(
1359                                                refcell_extra_stmts.borrow_mut().entry(*cluster_key).expect("location was removed").or_default(),
1360                                                &from_node, &sink_port,
1361                                                &to_node, &source_port,
1362                                                codec_type.0.as_ref(),
1363                                                format!("{}_{}", *from_external_key, *from_port_id)
1364                                            ),
1365                                        ),
1366                                        D::e2m_connect(&from_node, &sink_port, &to_node, &source_port, *port_hint),
1367                                    )
1368                                }
1369                                _ => panic!()
1370                            }
1371                        },
1372
1373                        DebugInstantiate::Finalized(_) => panic!("network already finalized"),
1374                    };
1375
1376                    *instantiate_fn = DebugInstantiateFinalized {
1377                        sink: sink_expr,
1378                        source: source_expr,
1379                        connect_fn: Some(connect_fn),
1380                    }
1381                    .into();
1382                } else if let HydroNode::Source { source: HydroSource::Embedded(ident), metadata } = n {
1383                    let element_type = match &metadata.collection_kind {
1384                        CollectionKind::Stream { element_type, .. } => element_type.0.as_ref().clone(),
1385                        _ => panic!("Embedded source must have Stream collection kind"),
1386                    };
1387                    let location_key = match metadata.location_id.root() {
1388                        LocationId::Process(key) | LocationId::Cluster(key) => *key,
1389                        _ => panic!("Embedded source must be on a process or cluster"),
1390                    };
1391                    D::register_embedded_stream_input(
1392                        &mut refcell_env.borrow_mut(),
1393                        location_key,
1394                        ident,
1395                        &element_type,
1396                    );
1397                } else if let HydroNode::Source { source: HydroSource::EmbeddedSingleton(ident), metadata } = n {
1398                    let element_type = match &metadata.collection_kind {
1399                        CollectionKind::Singleton { element_type, .. } => element_type.0.as_ref().clone(),
1400                        _ => panic!("EmbeddedSingleton source must have Singleton collection kind"),
1401                    };
1402                    let location_key = match metadata.location_id.root() {
1403                        LocationId::Process(key) | LocationId::Cluster(key) => *key,
1404                        _ => panic!("EmbeddedSingleton source must be on a process or cluster"),
1405                    };
1406                    D::register_embedded_singleton_input(
1407                        &mut refcell_env.borrow_mut(),
1408                        location_key,
1409                        ident,
1410                        &element_type,
1411                    );
1412                } else if let HydroNode::Source { source: HydroSource::ClusterMembers(location_id, state), metadata } = n {
1413                    match state {
1414                        ClusterMembersState::Uninit => {
1415                            let at_location = metadata.location_id.root().clone();
1416                            let key = (at_location.clone(), location_id.key());
1417                            if refcell_seen_cluster_members.borrow_mut().insert(key) {
1418                                // First occurrence: call cluster_membership_stream and mark as Stream.
1419                                let expr = stageleft::QuotedWithContext::splice_untyped_ctx(
1420                                    D::cluster_membership_stream(&mut refcell_env.borrow_mut(), &at_location, location_id),
1421                                    &(),
1422                                );
1423                                *state = ClusterMembersState::Stream(expr.into());
1424                            } else {
1425                                // Already instantiated for this (at, target) pair: just tee.
1426                                *state = ClusterMembersState::Tee(at_location, location_id.clone());
1427                            }
1428                        }
1429                        ClusterMembersState::Stream(_) | ClusterMembersState::Tee(..) => {
1430                            panic!("cluster members already finalized");
1431                        }
1432                    }
1433                }
1434            },
1435            seen_tees,
1436            false,
1437        );
1438    }
1439
1440    pub fn connect_network(&mut self, seen_tees: &mut SeenSharedNodes) {
1441        self.transform_bottom_up(
1442            &mut |l| {
1443                if let HydroRoot::SendExternal { instantiate_fn, .. } = l {
1444                    match instantiate_fn {
1445                        DebugInstantiate::Building => panic!("network not built"),
1446
1447                        DebugInstantiate::Finalized(finalized) => {
1448                            (finalized.connect_fn.take().unwrap())();
1449                        }
1450                    }
1451                }
1452            },
1453            &mut |n| {
1454                if let HydroNode::Network { instantiate_fn, .. }
1455                | HydroNode::ExternalInput { instantiate_fn, .. } = n
1456                {
1457                    match instantiate_fn {
1458                        DebugInstantiate::Building => panic!("network not built"),
1459
1460                        DebugInstantiate::Finalized(finalized) => {
1461                            (finalized.connect_fn.take().unwrap())();
1462                        }
1463                    }
1464                }
1465            },
1466            seen_tees,
1467            false,
1468        );
1469    }
1470
1471    pub fn transform_bottom_up(
1472        &mut self,
1473        transform_root: &mut impl FnMut(&mut HydroRoot),
1474        transform_node: &mut impl FnMut(&mut HydroNode),
1475        seen_tees: &mut SeenSharedNodes,
1476        check_well_formed: bool,
1477    ) {
1478        self.transform_children(
1479            |n, s| n.transform_bottom_up(transform_node, s, check_well_formed),
1480            seen_tees,
1481        );
1482
1483        transform_root(self);
1484    }
1485
1486    pub fn transform_children(
1487        &mut self,
1488        mut transform: impl FnMut(&mut HydroNode, &mut SeenSharedNodes),
1489        seen_tees: &mut SeenSharedNodes,
1490    ) {
1491        match self {
1492            HydroRoot::ForEach { f, input, .. } => {
1493                f.transform_children(&mut transform, seen_tees);
1494                transform(input, seen_tees);
1495            }
1496            HydroRoot::SendExternal { input, .. }
1497            | HydroRoot::DestSink { input, .. }
1498            | HydroRoot::CycleSink { input, .. }
1499            | HydroRoot::EmbeddedOutput { input, .. }
1500            | HydroRoot::Null { input, .. } => {
1501                transform(input, seen_tees);
1502            }
1503        }
1504    }
1505
1506    pub fn deep_clone(&self, seen_tees: &mut SeenSharedNodes) -> HydroRoot {
1507        match self {
1508            HydroRoot::ForEach {
1509                f,
1510                input,
1511                op_metadata,
1512            } => HydroRoot::ForEach {
1513                f: f.deep_clone(seen_tees),
1514                input: Box::new(input.deep_clone(seen_tees)),
1515                op_metadata: op_metadata.clone(),
1516            },
1517            HydroRoot::SendExternal {
1518                to_external_key,
1519                to_port_id,
1520                to_many,
1521                unpaired,
1522                serialize_fn,
1523                instantiate_fn,
1524                input,
1525                op_metadata,
1526            } => HydroRoot::SendExternal {
1527                to_external_key: *to_external_key,
1528                to_port_id: *to_port_id,
1529                to_many: *to_many,
1530                unpaired: *unpaired,
1531                serialize_fn: serialize_fn.clone(),
1532                instantiate_fn: instantiate_fn.clone(),
1533                input: Box::new(input.deep_clone(seen_tees)),
1534                op_metadata: op_metadata.clone(),
1535            },
1536            HydroRoot::DestSink {
1537                sink,
1538                input,
1539                op_metadata,
1540            } => HydroRoot::DestSink {
1541                sink: sink.clone(),
1542                input: Box::new(input.deep_clone(seen_tees)),
1543                op_metadata: op_metadata.clone(),
1544            },
1545            HydroRoot::CycleSink {
1546                cycle_id,
1547                input,
1548                op_metadata,
1549            } => HydroRoot::CycleSink {
1550                cycle_id: *cycle_id,
1551                input: Box::new(input.deep_clone(seen_tees)),
1552                op_metadata: op_metadata.clone(),
1553            },
1554            HydroRoot::EmbeddedOutput {
1555                ident,
1556                input,
1557                op_metadata,
1558            } => HydroRoot::EmbeddedOutput {
1559                ident: ident.clone(),
1560                input: Box::new(input.deep_clone(seen_tees)),
1561                op_metadata: op_metadata.clone(),
1562            },
1563            HydroRoot::Null { input, op_metadata } => HydroRoot::Null {
1564                input: Box::new(input.deep_clone(seen_tees)),
1565                op_metadata: op_metadata.clone(),
1566            },
1567        }
1568    }
1569
1570    #[cfg(feature = "build")]
1571    pub fn emit(
1572        &mut self,
1573        graph_builders: &mut dyn DfirBuilder,
1574        seen_tees: &mut SeenSharedNodes,
1575        built_tees: &mut HashMap<*const RefCell<HydroNode>, Vec<syn::Ident>>,
1576        next_stmt_id: &mut crate::Counter<StmtId>,
1577        fold_hooked_idents: &mut HashSet<String>,
1578    ) {
1579        self.emit_core(
1580            &mut BuildersOrCallback::<
1581                fn(&mut HydroRoot, &mut crate::Counter<StmtId>),
1582                fn(&mut HydroNode, &mut crate::Counter<StmtId>),
1583            >::Builders(graph_builders),
1584            seen_tees,
1585            built_tees,
1586            next_stmt_id,
1587            fold_hooked_idents,
1588        );
1589    }
1590
1591    #[cfg(feature = "build")]
1592    pub fn emit_core(
1593        &mut self,
1594        builders_or_callback: &mut BuildersOrCallback<
1595            impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
1596            impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
1597        >,
1598        seen_tees: &mut SeenSharedNodes,
1599        built_tees: &mut HashMap<*const RefCell<HydroNode>, Vec<syn::Ident>>,
1600        next_stmt_id: &mut crate::Counter<StmtId>,
1601        fold_hooked_idents: &mut HashSet<String>,
1602    ) {
1603        match self {
1604            HydroRoot::ForEach { f, input, .. } => {
1605                let input_ident = input.emit_core(
1606                    builders_or_callback,
1607                    seen_tees,
1608                    built_tees,
1609                    next_stmt_id,
1610                    fold_hooked_idents,
1611                );
1612
1613                // for_each is always side-effecting, so we observe non-determinism
1614                // even when the closure does not capture a mut ref (unlike map/filter
1615                // which only observe when they have a mut ref).
1616                let input_ident = if !input.metadata().collection_kind.is_strict() {
1617                    let observe_stmt_id = next_stmt_id.get_and_increment();
1618                    let observe_ident =
1619                        syn::Ident::new(&format!("stream_{}", observe_stmt_id), Span::call_site());
1620                    if let BuildersOrCallback::Builders(graph_builders) = builders_or_callback {
1621                        graph_builders.observe_for_mut(
1622                            &input.metadata().location_id,
1623                            input_ident,
1624                            &input.metadata().collection_kind,
1625                            &observe_ident,
1626                            &input.metadata().op,
1627                        );
1628                    }
1629                    observe_ident
1630                } else {
1631                    input_ident
1632                };
1633
1634                // Emit each captured handoff reference (deduplicated via `built_tees` in the
1635                // `HydroNode::Reference` arm), so that references captured *only* by this
1636                // `for_each` closure are still materialized. This mirrors how node-level
1637                // operators (e.g. `map`) emit their closures' captured references as part of
1638                // their bottom-up traversal. This is done in both the Builders and Callback
1639                // paths so that statement IDs stay consistent between them.
1640                let mut ref_idents = Vec::new();
1641                for (ref_node, _is_mut) in f.singleton_refs.iter_mut() {
1642                    assert!(
1643                        matches!(ref_node, HydroNode::Reference { .. }),
1644                        "singleton_refs should only contain HydroNode::Reference"
1645                    );
1646                    ref_idents.push(ref_node.emit_core(
1647                        builders_or_callback,
1648                        seen_tees,
1649                        built_tees,
1650                        next_stmt_id,
1651                        fold_hooked_idents,
1652                    ));
1653                }
1654
1655                // Mint the root's statement ID only after emitting the captured refs, so that
1656                // statement IDs follow emission order and (in the Callback path) the callback
1657                // observes this root's ID as the most recently allocated one, consistent with
1658                // the other `HydroRoot` variants.
1659                let stmt_id = next_stmt_id.get_and_increment();
1660
1661                match builders_or_callback {
1662                    BuildersOrCallback::Builders(graph_builders) => {
1663                        // The refs' idents are in `singleton_refs` order, matching what
1664                        // `emit_tokens` expects on the ident stack.
1665                        let mut ident_stack: Vec<syn::Ident> = ref_idents;
1666
1667                        let f_tokens = f.emit_tokens(&mut ident_stack);
1668
1669                        graph_builders.add_dfir_at(
1670                            &input.metadata().location_id,
1671                            parse_quote! {
1672                                #input_ident -> for_each(#f_tokens);
1673                            },
1674                            Some(&stmt_id.to_string()),
1675                        );
1676                    }
1677                    BuildersOrCallback::Callback(leaf_callback, _) => {
1678                        leaf_callback(self, next_stmt_id);
1679                    }
1680                }
1681            }
1682
1683            HydroRoot::SendExternal {
1684                serialize_fn,
1685                instantiate_fn,
1686                input,
1687                ..
1688            } => {
1689                let input_ident = input.emit_core(
1690                    builders_or_callback,
1691                    seen_tees,
1692                    built_tees,
1693                    next_stmt_id,
1694                    fold_hooked_idents,
1695                );
1696
1697                let stmt_id = next_stmt_id.get_and_increment();
1698
1699                match builders_or_callback {
1700                    BuildersOrCallback::Builders(graph_builders) => {
1701                        let (sink_expr, _) = match instantiate_fn {
1702                            DebugInstantiate::Building => (
1703                                syn::parse_quote!(DUMMY_SINK),
1704                                syn::parse_quote!(DUMMY_SOURCE),
1705                            ),
1706
1707                            DebugInstantiate::Finalized(finalized) => {
1708                                (finalized.sink.clone(), finalized.source.clone())
1709                            }
1710                        };
1711
1712                        graph_builders.create_external_output(
1713                            &input.metadata().location_id,
1714                            sink_expr,
1715                            &input_ident,
1716                            serialize_fn.as_ref(),
1717                            stmt_id,
1718                        );
1719                    }
1720                    BuildersOrCallback::Callback(leaf_callback, _) => {
1721                        leaf_callback(self, next_stmt_id);
1722                    }
1723                }
1724            }
1725
1726            HydroRoot::DestSink { sink, input, .. } => {
1727                let input_ident = input.emit_core(
1728                    builders_or_callback,
1729                    seen_tees,
1730                    built_tees,
1731                    next_stmt_id,
1732                    fold_hooked_idents,
1733                );
1734
1735                let stmt_id = next_stmt_id.get_and_increment();
1736
1737                match builders_or_callback {
1738                    BuildersOrCallback::Builders(graph_builders) => {
1739                        graph_builders.add_dfir_at(
1740                            &input.metadata().location_id,
1741                            parse_quote! {
1742                                #input_ident -> dest_sink(#sink);
1743                            },
1744                            Some(&stmt_id.to_string()),
1745                        );
1746                    }
1747                    BuildersOrCallback::Callback(leaf_callback, _) => {
1748                        leaf_callback(self, next_stmt_id);
1749                    }
1750                }
1751            }
1752
1753            HydroRoot::CycleSink {
1754                cycle_id, input, ..
1755            } => {
1756                let input_ident = input.emit_core(
1757                    builders_or_callback,
1758                    seen_tees,
1759                    built_tees,
1760                    next_stmt_id,
1761                    fold_hooked_idents,
1762                );
1763
1764                match builders_or_callback {
1765                    BuildersOrCallback::Builders(graph_builders) => {
1766                        let elem_type: syn::Type = match &input.metadata().collection_kind {
1767                            CollectionKind::KeyedSingleton {
1768                                key_type,
1769                                value_type,
1770                                ..
1771                            }
1772                            | CollectionKind::KeyedStream {
1773                                key_type,
1774                                value_type,
1775                                ..
1776                            } => {
1777                                parse_quote!((#key_type, #value_type))
1778                            }
1779                            CollectionKind::Stream { element_type, .. }
1780                            | CollectionKind::Singleton { element_type, .. }
1781                            | CollectionKind::Optional { element_type, .. } => {
1782                                parse_quote!(#element_type)
1783                            }
1784                        };
1785
1786                        let cycle_id_ident = cycle_id.as_ident();
1787                        graph_builders.add_dfir_at(
1788                            &input.metadata().location_id,
1789                            parse_quote! {
1790                                #cycle_id_ident = #input_ident -> identity::<#elem_type>();
1791                            },
1792                            None,
1793                        );
1794                    }
1795                    // No ID, no callback
1796                    BuildersOrCallback::Callback(_, _) => {}
1797                }
1798            }
1799
1800            HydroRoot::EmbeddedOutput { ident, input, .. } => {
1801                let input_ident = input.emit_core(
1802                    builders_or_callback,
1803                    seen_tees,
1804                    built_tees,
1805                    next_stmt_id,
1806                    fold_hooked_idents,
1807                );
1808
1809                let stmt_id = next_stmt_id.get_and_increment();
1810
1811                match builders_or_callback {
1812                    BuildersOrCallback::Builders(graph_builders) => {
1813                        graph_builders.add_dfir_at(
1814                            &input.metadata().location_id,
1815                            parse_quote! {
1816                                #input_ident -> for_each(&mut #ident);
1817                            },
1818                            Some(&stmt_id.to_string()),
1819                        );
1820                    }
1821                    BuildersOrCallback::Callback(leaf_callback, _) => {
1822                        leaf_callback(self, next_stmt_id);
1823                    }
1824                }
1825            }
1826
1827            HydroRoot::Null { input, .. } => {
1828                let input_ident = input.emit_core(
1829                    builders_or_callback,
1830                    seen_tees,
1831                    built_tees,
1832                    next_stmt_id,
1833                    fold_hooked_idents,
1834                );
1835
1836                let stmt_id = next_stmt_id.get_and_increment();
1837
1838                match builders_or_callback {
1839                    BuildersOrCallback::Builders(graph_builders) => {
1840                        graph_builders.add_dfir_at(
1841                            &input.metadata().location_id,
1842                            parse_quote! {
1843                                #input_ident -> for_each(|_| {});
1844                            },
1845                            Some(&stmt_id.to_string()),
1846                        );
1847                    }
1848                    BuildersOrCallback::Callback(leaf_callback, _) => {
1849                        leaf_callback(self, next_stmt_id);
1850                    }
1851                }
1852            }
1853        }
1854    }
1855
1856    pub fn op_metadata(&self) -> &HydroIrOpMetadata {
1857        match self {
1858            HydroRoot::ForEach { op_metadata, .. }
1859            | HydroRoot::SendExternal { op_metadata, .. }
1860            | HydroRoot::DestSink { op_metadata, .. }
1861            | HydroRoot::CycleSink { op_metadata, .. }
1862            | HydroRoot::EmbeddedOutput { op_metadata, .. }
1863            | HydroRoot::Null { op_metadata, .. } => op_metadata,
1864        }
1865    }
1866
1867    pub fn op_metadata_mut(&mut self) -> &mut HydroIrOpMetadata {
1868        match self {
1869            HydroRoot::ForEach { op_metadata, .. }
1870            | HydroRoot::SendExternal { op_metadata, .. }
1871            | HydroRoot::DestSink { op_metadata, .. }
1872            | HydroRoot::CycleSink { op_metadata, .. }
1873            | HydroRoot::EmbeddedOutput { op_metadata, .. }
1874            | HydroRoot::Null { op_metadata, .. } => op_metadata,
1875        }
1876    }
1877
1878    pub fn input(&self) -> &HydroNode {
1879        match self {
1880            HydroRoot::ForEach { input, .. }
1881            | HydroRoot::SendExternal { input, .. }
1882            | HydroRoot::DestSink { input, .. }
1883            | HydroRoot::CycleSink { input, .. }
1884            | HydroRoot::EmbeddedOutput { input, .. }
1885            | HydroRoot::Null { input, .. } => input,
1886        }
1887    }
1888
1889    pub fn input_metadata(&self) -> &HydroIrMetadata {
1890        self.input().metadata()
1891    }
1892
1893    pub fn print_root(&self) -> String {
1894        match self {
1895            HydroRoot::ForEach { f, .. } => format!("ForEach({:?})", f),
1896            HydroRoot::SendExternal { .. } => "SendExternal".to_owned(),
1897            HydroRoot::DestSink { sink, .. } => format!("DestSink({:?})", sink),
1898            HydroRoot::CycleSink { cycle_id, .. } => format!("CycleSink({})", cycle_id),
1899            HydroRoot::EmbeddedOutput { ident, .. } => {
1900                format!("EmbeddedOutput({})", ident)
1901            }
1902            HydroRoot::Null { .. } => "Null".to_owned(),
1903        }
1904    }
1905
1906    pub fn visit_debug_expr(&mut self, mut transform: impl FnMut(&mut DebugExpr)) {
1907        match self {
1908            HydroRoot::ForEach { f, .. } => {
1909                transform(&mut f.expr);
1910            }
1911            HydroRoot::DestSink { sink, .. } => {
1912                transform(sink);
1913            }
1914            HydroRoot::SendExternal { .. }
1915            | HydroRoot::CycleSink { .. }
1916            | HydroRoot::EmbeddedOutput { .. }
1917            | HydroRoot::Null { .. } => {}
1918        }
1919    }
1920}
1921
1922#[cfg(feature = "build")]
1923fn tick_of(loc: &LocationId) -> Option<ClockId> {
1924    match loc {
1925        LocationId::Tick(id, _) => Some(*id),
1926        LocationId::Atomic(inner) => tick_of(inner),
1927        _ => None,
1928    }
1929}
1930
1931#[cfg(feature = "build")]
1932fn remap_location(loc: &mut LocationId, uf: &mut HashMap<ClockId, ClockId>) {
1933    match loc {
1934        LocationId::Tick(id, inner) => {
1935            *id = uf_find(uf, *id);
1936            remap_location(inner, uf);
1937        }
1938        LocationId::Atomic(inner) => {
1939            remap_location(inner, uf);
1940        }
1941        LocationId::Process(_) | LocationId::Cluster(_) => {}
1942    }
1943}
1944
1945#[cfg(feature = "build")]
1946fn uf_find(parent: &mut HashMap<ClockId, ClockId>, x: ClockId) -> ClockId {
1947    let p = *parent.get(&x).unwrap_or(&x);
1948    if p == x {
1949        return x;
1950    }
1951    let root = uf_find(parent, p);
1952    parent.insert(x, root);
1953    root
1954}
1955
1956#[cfg(feature = "build")]
1957fn uf_union(parent: &mut HashMap<ClockId, ClockId>, a: ClockId, b: ClockId) {
1958    let ra = uf_find(parent, a);
1959    let rb = uf_find(parent, b);
1960    if ra != rb {
1961        parent.insert(ra, rb);
1962    }
1963}
1964
1965/// Traverse the IR to build a union-find that unifies tick IDs connected
1966/// through `Batch` and `YieldConcat` nodes at atomic boundaries, then
1967/// rewrite all `LocationId`s to use the representative tick ID.
1968#[cfg(feature = "build")]
1969pub fn unify_atomic_ticks(ir: &mut [HydroRoot]) {
1970    let mut uf: HashMap<ClockId, ClockId> = HashMap::new();
1971
1972    // Pass 1: collect unifications.
1973    transform_bottom_up(
1974        ir,
1975        &mut |_| {},
1976        &mut |node: &mut HydroNode| match node {
1977            HydroNode::Batch { inner, metadata } | HydroNode::YieldConcat { inner, metadata } => {
1978                if let (Some(a), Some(b)) = (
1979                    tick_of(&inner.metadata().location_id),
1980                    tick_of(&metadata.location_id),
1981                ) {
1982                    uf_union(&mut uf, a, b);
1983                }
1984            }
1985            HydroNode::Chain {
1986                first,
1987                second,
1988                metadata,
1989            }
1990            | HydroNode::ChainFirst {
1991                first,
1992                second,
1993                metadata,
1994            }
1995            | HydroNode::MergeOrdered {
1996                first,
1997                second,
1998                metadata,
1999            } => {
2000                if let (Some(a), Some(b)) = (
2001                    tick_of(&first.metadata().location_id),
2002                    tick_of(&metadata.location_id),
2003                ) {
2004                    uf_union(&mut uf, a, b);
2005                }
2006                if let (Some(a), Some(b)) = (
2007                    tick_of(&second.metadata().location_id),
2008                    tick_of(&metadata.location_id),
2009                ) {
2010                    uf_union(&mut uf, a, b);
2011                }
2012            }
2013            _ => {}
2014        },
2015        false,
2016    );
2017
2018    // Pass 2: rewrite all LocationIds.
2019    transform_bottom_up(
2020        ir,
2021        &mut |_| {},
2022        &mut |node: &mut HydroNode| {
2023            remap_location(&mut node.metadata_mut().location_id, &mut uf);
2024        },
2025        false,
2026    );
2027}
2028
2029#[cfg(feature = "build")]
2030pub fn emit(ir: &mut Vec<HydroRoot>) -> SecondaryMap<LocationKey, FlatGraphBuilder> {
2031    let mut builders = ProdDfirBuilder::default();
2032    let mut seen_tees = HashMap::new();
2033    let mut built_tees = HashMap::new();
2034    let mut next_stmt_id = crate::Counter::<StmtId>::default();
2035    let mut fold_hooked_idents = HashSet::new();
2036    for leaf in ir {
2037        leaf.emit(
2038            &mut builders,
2039            &mut seen_tees,
2040            &mut built_tees,
2041            &mut next_stmt_id,
2042            &mut fold_hooked_idents,
2043        );
2044    }
2045    builders.graphs
2046}
2047
2048#[cfg(feature = "build")]
2049pub fn traverse_dfir(
2050    ir: &mut [HydroRoot],
2051    transform_root: impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
2052    transform_node: impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
2053) {
2054    let mut seen_tees = HashMap::new();
2055    let mut built_tees = HashMap::new();
2056    let mut next_stmt_id = crate::Counter::<StmtId>::default();
2057    let mut fold_hooked_idents = HashSet::new();
2058    let mut callback = BuildersOrCallback::Callback(transform_root, transform_node);
2059    ir.iter_mut().for_each(|leaf| {
2060        leaf.emit_core(
2061            &mut callback,
2062            &mut seen_tees,
2063            &mut built_tees,
2064            &mut next_stmt_id,
2065            &mut fold_hooked_idents,
2066        );
2067    });
2068}
2069
2070pub fn transform_bottom_up(
2071    ir: &mut [HydroRoot],
2072    transform_root: &mut impl FnMut(&mut HydroRoot),
2073    transform_node: &mut impl FnMut(&mut HydroNode),
2074    check_well_formed: bool,
2075) {
2076    let mut seen_tees = HashMap::new();
2077    ir.iter_mut().for_each(|leaf| {
2078        leaf.transform_bottom_up(
2079            transform_root,
2080            transform_node,
2081            &mut seen_tees,
2082            check_well_formed,
2083        );
2084    });
2085}
2086
2087pub fn deep_clone(ir: &[HydroRoot]) -> Vec<HydroRoot> {
2088    let mut seen_tees = HashMap::new();
2089    ir.iter()
2090        .map(|leaf| leaf.deep_clone(&mut seen_tees))
2091        .collect()
2092}
2093
2094type PrintedTees = RefCell<Option<(usize, HashMap<*const RefCell<HydroNode>, usize>)>>;
2095thread_local! {
2096    static PRINTED_TEES: PrintedTees = const { RefCell::new(None) };
2097    /// Tracks shared nodes already serialized so that `SharedNode::serialize`
2098    /// emits the full subtree only once and uses a `"<shared N>"` back-reference
2099    /// on subsequent encounters, preventing infinite loops.
2100    static SERIALIZED_SHARED: PrintedTees
2101        = const { RefCell::new(None) };
2102}
2103
2104pub fn dbg_dedup_tee<T>(f: impl FnOnce() -> T) -> T {
2105    PRINTED_TEES.with(|printed_tees| {
2106        let mut printed_tees_mut = printed_tees.borrow_mut();
2107        *printed_tees_mut = Some((0, HashMap::new()));
2108        drop(printed_tees_mut);
2109
2110        let ret = f();
2111
2112        let mut printed_tees_mut = printed_tees.borrow_mut();
2113        *printed_tees_mut = None;
2114
2115        ret
2116    })
2117}
2118
2119/// Runs `f` with a fresh shared-node deduplication scope for serialization.
2120/// Any `SharedNode` serialized inside `f` will be tracked; the first occurrence
2121/// emits the full subtree while later occurrences emit a `{"$shared_ref": id}`
2122/// back-reference.  The tracking state is restored when `f` returns or panics.
2123pub fn serialize_dedup_shared<T>(f: impl FnOnce() -> T) -> T {
2124    let _guard = SerializedSharedGuard::enter();
2125    f()
2126}
2127
2128/// RAII guard that saves/restores the `SERIALIZED_SHARED` thread-local,
2129/// making `serialize_dedup_shared` re-entrant and panic-safe.
2130struct SerializedSharedGuard {
2131    previous: Option<(usize, HashMap<*const RefCell<HydroNode>, usize>)>,
2132}
2133
2134impl SerializedSharedGuard {
2135    fn enter() -> Self {
2136        let previous = SERIALIZED_SHARED.with(|cell| {
2137            let mut guard = cell.borrow_mut();
2138            guard.replace((0, HashMap::new()))
2139        });
2140        Self { previous }
2141    }
2142}
2143
2144impl Drop for SerializedSharedGuard {
2145    fn drop(&mut self) {
2146        SERIALIZED_SHARED.with(|cell| {
2147            *cell.borrow_mut() = self.previous.take();
2148        });
2149    }
2150}
2151
2152pub struct SharedNode(pub Rc<RefCell<HydroNode>>);
2153
2154impl serde::Serialize for SharedNode {
2155    /// Multiple `SharedNode`s can point to the same underlying `HydroNode` (via
2156    /// `Tee` / `Partition`).  A naïve recursive serialization would revisit the
2157    /// same subtree every time and, if the graph ever contains a cycle, loop
2158    /// forever.
2159    ///
2160    /// We keep a thread-local map (`SERIALIZED_SHARED`) from raw `Rc` pointer →
2161    /// integer id.  The first time we see a pointer we assign it the next id and
2162    /// emit the full subtree as `{"$shared": <id>, "node": …}`.  Every later
2163    /// encounter of the same pointer emits `{"$shared_ref": <id>}`, cutting the
2164    /// recursion.  Requires an active `serialize_dedup_shared` scope.
2165    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2166        SERIALIZED_SHARED.with(|cell| {
2167            let mut guard = cell.borrow_mut();
2168            // (next_id, pointer → assigned_id)
2169            let state = guard.as_mut().ok_or_else(|| {
2170                serde::ser::Error::custom(
2171                    "SharedNode serialization requires an active serialize_dedup_shared scope",
2172                )
2173            })?;
2174            let ptr = self.0.as_ptr() as *const RefCell<HydroNode>;
2175
2176            if let Some(&id) = state.1.get(&ptr) {
2177                drop(guard);
2178                use serde::ser::SerializeMap;
2179                let mut map = serializer.serialize_map(Some(1))?;
2180                map.serialize_entry("$shared_ref", &id)?;
2181                map.end()
2182            } else {
2183                let id = state.0;
2184                state.0 += 1;
2185                state.1.insert(ptr, id);
2186                drop(guard);
2187
2188                use serde::ser::SerializeMap;
2189                let mut map = serializer.serialize_map(Some(2))?;
2190                map.serialize_entry("$shared", &id)?;
2191                map.serialize_entry("node", &*self.0.borrow())?;
2192                map.end()
2193            }
2194        })
2195    }
2196}
2197
2198impl SharedNode {
2199    pub fn as_ptr(&self) -> *const RefCell<HydroNode> {
2200        Rc::as_ptr(&self.0)
2201    }
2202}
2203
2204impl Debug for SharedNode {
2205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2206        PRINTED_TEES.with(|printed_tees| {
2207            let mut printed_tees_mut_borrow = printed_tees.borrow_mut();
2208            let printed_tees_mut = printed_tees_mut_borrow.as_mut();
2209
2210            if let Some(printed_tees_mut) = printed_tees_mut {
2211                if let Some(existing) = printed_tees_mut
2212                    .1
2213                    .get(&(self.0.as_ref() as *const RefCell<HydroNode>))
2214                {
2215                    write!(f, "<shared {}>", existing)
2216                } else {
2217                    let next_id = printed_tees_mut.0;
2218                    printed_tees_mut.0 += 1;
2219                    printed_tees_mut
2220                        .1
2221                        .insert(self.0.as_ref() as *const RefCell<HydroNode>, next_id);
2222                    drop(printed_tees_mut_borrow);
2223                    write!(f, "<shared {}>: ", next_id)?;
2224                    Debug::fmt(&self.0.borrow(), f)
2225                }
2226            } else {
2227                drop(printed_tees_mut_borrow);
2228                write!(f, "<shared>: ")?;
2229                Debug::fmt(&self.0.borrow(), f)
2230            }
2231        })
2232    }
2233}
2234
2235impl Hash for SharedNode {
2236    fn hash<H: Hasher>(&self, state: &mut H) {
2237        self.0.borrow_mut().hash(state);
2238    }
2239}
2240
2241/// A counter for tracking singleton access groups on a `HydroNode::Reference`.
2242///
2243/// Each mutable access increments the counter (before and after) to isolate itself in its own group;
2244/// immutable accesses share the current group.
2245#[derive(Debug)]
2246pub enum AccessCounter {
2247    Counting(Cell<u32>),
2248    Frozen(u32),
2249}
2250
2251impl AccessCounter {
2252    pub fn new() -> Self {
2253        Self::Counting(Cell::new(0))
2254    }
2255
2256    /// Assign the next access group for this reference.
2257    /// Mutable accesses get an isolated group (counter increments before and after).
2258    /// Immutable accesses share the current group.
2259    pub fn next_group(&self, is_mut: bool) -> Self {
2260        let AccessCounter::Counting(count) = self else {
2261            panic!("Cannot count on `AccessCounter::Frozen`");
2262        };
2263        let c = if is_mut {
2264            let c = count.get() + 1;
2265            count.set(c + 1);
2266            c
2267        } else {
2268            count.get()
2269        };
2270        Self::Frozen(c)
2271    }
2272
2273    /// Creates a frozen counter to prevent further counting.
2274    pub fn freeze(&self) -> Self {
2275        Self::Frozen(match self {
2276            Self::Counting(count) => count.get(),
2277            Self::Frozen(count) => *count,
2278        })
2279    }
2280
2281    pub fn frozen_group(&self) -> u32 {
2282        let Self::Frozen(count) = self else {
2283            panic!("`AccessCounter` not frozen");
2284        };
2285        *count
2286    }
2287}
2288
2289impl Default for AccessCounter {
2290    fn default() -> Self {
2291        Self::new()
2292    }
2293}
2294
2295impl Hash for AccessCounter {
2296    fn hash<H: Hasher>(&self, _state: &mut H) {
2297        // Access counter does not participate in hashing — it is runtime bookkeeping.
2298    }
2299}
2300
2301impl serde::Serialize for AccessCounter {
2302    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2303        let count = match self {
2304            AccessCounter::Counting(count) => count.get(),
2305            AccessCounter::Frozen(count) => *count,
2306        };
2307        count.serialize(serializer)
2308    }
2309}
2310
2311#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2312pub enum BoundKind {
2313    Unbounded,
2314    Bounded,
2315}
2316
2317#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2318pub enum StreamOrder {
2319    NoOrder,
2320    TotalOrder,
2321}
2322
2323#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2324pub enum StreamRetry {
2325    AtLeastOnce,
2326    ExactlyOnce,
2327}
2328
2329#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2330pub enum KeyedSingletonBoundKind {
2331    Unbounded,
2332    MonotonicKeys,
2333    MonotonicValue,
2334    BoundedValue,
2335    Bounded,
2336}
2337
2338#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
2339pub enum SingletonBoundKind {
2340    Unbounded,
2341    Monotonic,
2342    Bounded,
2343}
2344
2345#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize)]
2346pub enum CollectionKind {
2347    Stream {
2348        bound: BoundKind,
2349        order: StreamOrder,
2350        retry: StreamRetry,
2351        element_type: DebugType,
2352    },
2353    Singleton {
2354        bound: SingletonBoundKind,
2355        element_type: DebugType,
2356    },
2357    Optional {
2358        bound: BoundKind,
2359        element_type: DebugType,
2360    },
2361    KeyedStream {
2362        bound: BoundKind,
2363        value_order: StreamOrder,
2364        value_retry: StreamRetry,
2365        key_type: DebugType,
2366        value_type: DebugType,
2367    },
2368    KeyedSingleton {
2369        bound: KeyedSingletonBoundKind,
2370        key_type: DebugType,
2371        value_type: DebugType,
2372    },
2373}
2374
2375impl CollectionKind {
2376    pub fn is_bounded(&self) -> bool {
2377        matches!(
2378            self,
2379            CollectionKind::Stream {
2380                bound: BoundKind::Bounded,
2381                ..
2382            } | CollectionKind::Singleton {
2383                bound: SingletonBoundKind::Bounded,
2384                ..
2385            } | CollectionKind::Optional {
2386                bound: BoundKind::Bounded,
2387                ..
2388            } | CollectionKind::KeyedStream {
2389                bound: BoundKind::Bounded,
2390                ..
2391            } | CollectionKind::KeyedSingleton {
2392                bound: KeyedSingletonBoundKind::Bounded,
2393                ..
2394            }
2395        )
2396    }
2397
2398    /// Returns whether this collection kind is already "strict" (TotalOrder + ExactlyOnce),
2399    /// meaning no non-determinism needs to be observed for mut closures.
2400    pub fn is_strict(&self) -> bool {
2401        match self {
2402            CollectionKind::Stream { order, retry, .. } => {
2403                *order == StreamOrder::TotalOrder && *retry == StreamRetry::ExactlyOnce
2404            }
2405            CollectionKind::KeyedStream {
2406                value_order,
2407                value_retry,
2408                ..
2409            } => {
2410                *value_order == StreamOrder::TotalOrder && *value_retry == StreamRetry::ExactlyOnce
2411            }
2412            // Singletons/Optionals/KeyedSingletons do not have observable
2413            // non-determinism other than snapshots / batching
2414            CollectionKind::Singleton { .. }
2415            | CollectionKind::Optional { .. }
2416            | CollectionKind::KeyedSingleton { .. } => true,
2417        }
2418    }
2419
2420    /// Creates a "strict" version of this kind with TotalOrder and ExactlyOnce.
2421    pub fn strict_kind(&self) -> CollectionKind {
2422        match self {
2423            CollectionKind::Stream {
2424                bound,
2425                element_type,
2426                ..
2427            } => CollectionKind::Stream {
2428                bound: bound.clone(),
2429                order: StreamOrder::TotalOrder,
2430                retry: StreamRetry::ExactlyOnce,
2431                element_type: element_type.clone(),
2432            },
2433            CollectionKind::KeyedStream {
2434                bound,
2435                key_type,
2436                value_type,
2437                ..
2438            } => CollectionKind::KeyedStream {
2439                bound: bound.clone(),
2440                value_order: StreamOrder::TotalOrder,
2441                value_retry: StreamRetry::ExactlyOnce,
2442                key_type: key_type.clone(),
2443                value_type: value_type.clone(),
2444            },
2445            other => other.clone(),
2446        }
2447    }
2448}
2449
2450#[derive(Clone, serde::Serialize)]
2451pub struct HydroIrMetadata {
2452    pub location_id: LocationId,
2453    pub collection_kind: CollectionKind,
2454    pub consistency: Option<ClusterConsistency>,
2455    pub cardinality: Option<usize>,
2456    pub tag: Option<String>,
2457    pub op: HydroIrOpMetadata,
2458}
2459
2460// HydroIrMetadata shouldn't be used to hash or compare
2461impl Hash for HydroIrMetadata {
2462    fn hash<H: Hasher>(&self, _: &mut H) {}
2463}
2464
2465impl PartialEq for HydroIrMetadata {
2466    fn eq(&self, _: &Self) -> bool {
2467        true
2468    }
2469}
2470
2471impl Eq for HydroIrMetadata {}
2472
2473impl Debug for HydroIrMetadata {
2474    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2475        f.debug_struct("HydroIrMetadata")
2476            .field("location_id", &self.location_id)
2477            .field("collection_kind", &self.collection_kind)
2478            .finish()
2479    }
2480}
2481
2482/// Metadata that is specific to the operator itself, rather than its outputs.
2483/// This is available on _both_ inner nodes and roots.
2484#[derive(Clone, serde::Serialize)]
2485pub struct HydroIrOpMetadata {
2486    #[serde(rename = "span", serialize_with = "serialize_backtrace_as_span")]
2487    pub backtrace: Backtrace,
2488    pub cpu_usage: Option<f64>,
2489    pub network_recv_cpu_usage: Option<f64>,
2490    pub id: Option<usize>,
2491}
2492
2493impl HydroIrOpMetadata {
2494    #[expect(
2495        clippy::new_without_default,
2496        reason = "explicit calls to new ensure correct backtrace bounds"
2497    )]
2498    pub fn new() -> HydroIrOpMetadata {
2499        Self::new_with_skip(1)
2500    }
2501
2502    fn new_with_skip(skip_count: usize) -> HydroIrOpMetadata {
2503        HydroIrOpMetadata {
2504            backtrace: Backtrace::get_backtrace(2 + skip_count),
2505            cpu_usage: None,
2506            network_recv_cpu_usage: None,
2507            id: None,
2508        }
2509    }
2510}
2511
2512impl Debug for HydroIrOpMetadata {
2513    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2514        f.debug_struct("HydroIrOpMetadata").finish()
2515    }
2516}
2517
2518impl Hash for HydroIrOpMetadata {
2519    fn hash<H: Hasher>(&self, _: &mut H) {}
2520}
2521
2522/// How a network channel's *sender* prepares each message before it is handed to the transport.
2523///
2524/// A channel's serialization is split into a send half ([`NetworkSend`]) and a receive half
2525/// ([`NetworkRecv`]) so that the multi-version simulation merge can reason about each side
2526/// independently (the sender fork and the receiver are separate IR nodes).
2527#[derive(Debug, Clone, Hash, serde::Serialize)]
2528pub enum NetworkSend {
2529    /// Serialization is performed within the Hydro dataflow using the provided serialize
2530    /// expression. This is how channels using [`crate::networking::Bincode`] are lowered.
2531    Custom { serialize_fn: Option<DebugExpr> },
2532    /// Serialization is left to code outside of Hydro (see [`crate::networking::Embedded`]). The
2533    /// raw `element_type` is passed through unserialized; the only transformation is converting a
2534    /// routing [`crate::location::MemberId`] (the destination cluster `tag`, when demuxing) into
2535    /// the raw `TaglessMemberId` used by the transport. Only supported by the embedded backend.
2536    ///
2537    /// Stored as structured info (rather than a pre-baked expression) so that the code can be
2538    /// synthesized in a post-IR codegen pass.
2539    Embedded {
2540        tag: Option<DebugType>,
2541        element_type: DebugType,
2542    },
2543}
2544
2545/// How a network channel's *receiver* recovers each message from the transport. See
2546/// [`NetworkSend`] for the sender half.
2547#[derive(Debug, Clone, Hash, serde::Serialize)]
2548pub enum NetworkRecv {
2549    /// Deserialization is performed within the Hydro dataflow using the provided deserialize
2550    /// expression. This is how channels using [`crate::networking::Bincode`] are lowered.
2551    Custom { deserialize_fn: Option<DebugExpr> },
2552    /// Deserialization is left to code outside of Hydro (see [`crate::networking::Embedded`]). The
2553    /// raw `element_type` is delivered to the receiver directly, with no transport `Result` to
2554    /// unwrap (the external code that produces the stream decides how to handle faults). The only
2555    /// transformation is converting a `TaglessMemberId` back into a typed
2556    /// [`crate::location::MemberId`] (the sender cluster `tag`, when the receiver is keyed by
2557    /// sender). Only supported by the embedded backend.
2558    Embedded {
2559        tag: Option<DebugType>,
2560        element_type: DebugType,
2561    },
2562}
2563
2564impl NetworkSend {
2565    /// The raw payload type flowing across the channel when serialization is left to external code,
2566    /// or [`None`] when the channel serializes internally.
2567    pub(crate) fn external_element_type(&self) -> Option<&syn::Type> {
2568        match self {
2569            NetworkSend::Custom { .. } => None,
2570            NetworkSend::Embedded { element_type, .. } => Some(&element_type.0),
2571        }
2572    }
2573}
2574
2575impl NetworkRecv {
2576    /// See [`NetworkSend::external_element_type`].
2577    pub(crate) fn external_element_type(&self) -> Option<&syn::Type> {
2578        match self {
2579            NetworkRecv::Custom { .. } => None,
2580            NetworkRecv::Embedded { element_type, .. } => Some(&element_type.0),
2581        }
2582    }
2583}
2584
2585#[cfg(feature = "build")]
2586impl NetworkSend {
2587    /// The expression applied on the sender to prepare each message for the transport, if any.
2588    pub(crate) fn pipeline(&self) -> Option<DebugExpr> {
2589        match self {
2590            NetworkSend::Custom { serialize_fn } => serialize_fn.clone(),
2591            NetworkSend::Embedded { tag, element_type } => {
2592                let root = crate::staging_util::get_this_crate();
2593                let element_type = &element_type.0;
2594                let expr: syn::Expr = if let Some(tag) = tag {
2595                    let tag = &tag.0;
2596                    parse_quote! {
2597                        #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<(#root::__staged::location::MemberId<#tag>, #element_type), _>(
2598                            |(id, data)| (id.into_tagless(), data)
2599                        )
2600                    }
2601                } else {
2602                    parse_quote! {
2603                        #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<#element_type, _>(
2604                            |data| data
2605                        )
2606                    }
2607                };
2608                Some(expr.into())
2609            }
2610        }
2611    }
2612}
2613
2614#[cfg(feature = "build")]
2615impl NetworkRecv {
2616    /// The expression applied on the receiver to recover each message from the transport, if any.
2617    pub(crate) fn pipeline(&self) -> Option<DebugExpr> {
2618        match self {
2619            NetworkRecv::Custom { deserialize_fn } => deserialize_fn.clone(),
2620            // Embedded channels hand the raw payload to the receiver directly (no transport
2621            // `Result`), so the developer's external code decides how to handle serialization
2622            // faults. The only transformation is restoring the typed `MemberId` when the receiver
2623            // is keyed by the sender.
2624            NetworkRecv::Embedded { tag, .. } => {
2625                let tag = tag.as_ref()?;
2626                let root = crate::staging_util::get_this_crate();
2627                let tag = &tag.0;
2628                let expr: syn::Expr = parse_quote! {
2629                    |(id, b)| (#root::__staged::location::MemberId::<#tag>::from_tagless(id as #root::__staged::location::TaglessMemberId), b)
2630                };
2631                Some(expr.into())
2632            }
2633        }
2634    }
2635}
2636
2637/// An intermediate node in a Hydro graph, which consumes data
2638/// from upstream nodes and emits data to downstream nodes.
2639#[derive(Debug, Hash, serde::Serialize)]
2640pub enum HydroNode {
2641    Placeholder,
2642
2643    /// Manually "casts" between two different collection kinds.
2644    ///
2645    /// Using this IR node requires special care, since it bypasses many of Hydro's core
2646    /// correctness checks. In particular, the user must ensure that every possible
2647    /// "interpretation" of the input corresponds to a distinct "interpretation" of the output,
2648    /// where an "interpretation" is a possible output of `ObserveNonDet` applied to the
2649    /// collection. This ensures that the simulator does not miss any possible outputs.
2650    Cast {
2651        inner: Box<HydroNode>,
2652        metadata: HydroIrMetadata,
2653    },
2654
2655    /// Strengthens the guarantees of a stream by non-deterministically selecting a possible
2656    /// interpretation of the input stream.
2657    ///
2658    /// In production, this simply passes through the input, but in simulation, this operator
2659    /// explicitly selects a randomized interpretation.
2660    ObserveNonDet {
2661        inner: Box<HydroNode>,
2662        trusted: bool, // if true, we do not need to simulate non-determinism
2663        metadata: HydroIrMetadata,
2664    },
2665
2666    Source {
2667        source: HydroSource,
2668        metadata: HydroIrMetadata,
2669    },
2670
2671    SingletonSource {
2672        value: DebugExpr,
2673        first_tick_only: bool,
2674        metadata: HydroIrMetadata,
2675    },
2676
2677    CycleSource {
2678        cycle_id: CycleId,
2679        metadata: HydroIrMetadata,
2680    },
2681
2682    Tee {
2683        inner: SharedNode,
2684        metadata: HydroIrMetadata,
2685    },
2686
2687    /// A reference materialization point. Wraps a SharedNode so that:
2688    /// - The pipe output delivers data to one consumer
2689    /// - `#var` references can borrow the value from the slot
2690    ///
2691    /// In DFIR codegen, emits `ident = inner_ident -> singleton()` or `-> optional()` or
2692    /// `-> handoff()` depending on `kind`.
2693    ///
2694    /// Uses the same `built_tees` dedup pattern as `Tee`.
2695    Reference {
2696        inner: SharedNode,
2697        kind: crate::handoff_ref::HandoffRefKind,
2698        access_counter: AccessCounter,
2699        metadata: HydroIrMetadata,
2700    },
2701
2702    Partition {
2703        inner: SharedNode,
2704        f: ClosureExpr,
2705        is_true: bool,
2706        metadata: HydroIrMetadata,
2707    },
2708
2709    BeginAtomic {
2710        inner: Box<HydroNode>,
2711        metadata: HydroIrMetadata,
2712    },
2713
2714    EndAtomic {
2715        inner: Box<HydroNode>,
2716        metadata: HydroIrMetadata,
2717    },
2718
2719    Batch {
2720        inner: Box<HydroNode>,
2721        metadata: HydroIrMetadata,
2722    },
2723
2724    YieldConcat {
2725        inner: Box<HydroNode>,
2726        metadata: HydroIrMetadata,
2727    },
2728
2729    Chain {
2730        first: Box<HydroNode>,
2731        second: Box<HydroNode>,
2732        metadata: HydroIrMetadata,
2733    },
2734
2735    MergeOrdered {
2736        first: Box<HydroNode>,
2737        second: Box<HydroNode>,
2738        metadata: HydroIrMetadata,
2739    },
2740
2741    ChainFirst {
2742        first: Box<HydroNode>,
2743        second: Box<HydroNode>,
2744        metadata: HydroIrMetadata,
2745    },
2746
2747    CrossProduct {
2748        left: Box<HydroNode>,
2749        right: Box<HydroNode>,
2750        metadata: HydroIrMetadata,
2751    },
2752
2753    CrossSingleton {
2754        left: Box<HydroNode>,
2755        right: Box<HydroNode>,
2756        metadata: HydroIrMetadata,
2757    },
2758
2759    Join {
2760        left: Box<HydroNode>,
2761        right: Box<HydroNode>,
2762        metadata: HydroIrMetadata,
2763    },
2764
2765    /// Asymmetric join where the right (build) side is bounded.
2766    /// The build side is accumulated (stratum-delayed) into a hash table,
2767    /// then the left (probe) side streams through preserving its ordering.
2768    JoinHalf {
2769        left: Box<HydroNode>,
2770        right: Box<HydroNode>,
2771        metadata: HydroIrMetadata,
2772    },
2773
2774    Difference {
2775        pos: Box<HydroNode>,
2776        neg: Box<HydroNode>,
2777        metadata: HydroIrMetadata,
2778    },
2779
2780    AntiJoin {
2781        pos: Box<HydroNode>,
2782        neg: Box<HydroNode>,
2783        metadata: HydroIrMetadata,
2784    },
2785
2786    ResolveFutures {
2787        input: Box<HydroNode>,
2788        metadata: HydroIrMetadata,
2789    },
2790    ResolveFuturesBlocking {
2791        input: Box<HydroNode>,
2792        metadata: HydroIrMetadata,
2793    },
2794    ResolveFuturesOrdered {
2795        input: Box<HydroNode>,
2796        metadata: HydroIrMetadata,
2797    },
2798
2799    Map {
2800        f: ClosureExpr,
2801        input: Box<HydroNode>,
2802        metadata: HydroIrMetadata,
2803    },
2804    FlatMap {
2805        f: ClosureExpr,
2806        input: Box<HydroNode>,
2807        metadata: HydroIrMetadata,
2808    },
2809    FlatMapStreamBlocking {
2810        f: ClosureExpr,
2811        input: Box<HydroNode>,
2812        metadata: HydroIrMetadata,
2813    },
2814    Filter {
2815        f: ClosureExpr,
2816        input: Box<HydroNode>,
2817        metadata: HydroIrMetadata,
2818    },
2819    FilterMap {
2820        f: ClosureExpr,
2821        input: Box<HydroNode>,
2822        metadata: HydroIrMetadata,
2823    },
2824
2825    DeferTick {
2826        input: Box<HydroNode>,
2827        metadata: HydroIrMetadata,
2828    },
2829    Enumerate {
2830        input: Box<HydroNode>,
2831        metadata: HydroIrMetadata,
2832    },
2833    Inspect {
2834        f: ClosureExpr,
2835        input: Box<HydroNode>,
2836        metadata: HydroIrMetadata,
2837    },
2838
2839    Unique {
2840        input: Box<HydroNode>,
2841        metadata: HydroIrMetadata,
2842    },
2843
2844    Sort {
2845        input: Box<HydroNode>,
2846        metadata: HydroIrMetadata,
2847    },
2848    Fold {
2849        init: ClosureExpr,
2850        acc: ClosureExpr,
2851        input: Box<HydroNode>,
2852        metadata: HydroIrMetadata,
2853    },
2854
2855    Scan {
2856        init: ClosureExpr,
2857        acc: ClosureExpr,
2858        input: Box<HydroNode>,
2859        metadata: HydroIrMetadata,
2860    },
2861    ScanAsyncBlocking {
2862        init: ClosureExpr,
2863        acc: ClosureExpr,
2864        input: Box<HydroNode>,
2865        metadata: HydroIrMetadata,
2866    },
2867    FoldKeyed {
2868        init: ClosureExpr,
2869        acc: ClosureExpr,
2870        input: Box<HydroNode>,
2871        metadata: HydroIrMetadata,
2872    },
2873
2874    Reduce {
2875        f: ClosureExpr,
2876        input: Box<HydroNode>,
2877        metadata: HydroIrMetadata,
2878    },
2879    ReduceKeyed {
2880        f: ClosureExpr,
2881        input: Box<HydroNode>,
2882        metadata: HydroIrMetadata,
2883    },
2884    ReduceKeyedWatermark {
2885        f: ClosureExpr,
2886        input: Box<HydroNode>,
2887        watermark: Box<HydroNode>,
2888        metadata: HydroIrMetadata,
2889    },
2890
2891    Network {
2892        name: Option<String>,
2893        networking_info: crate::networking::NetworkingInfo,
2894        serialize: NetworkSend,
2895        deserialize: NetworkRecv,
2896        instantiate_fn: DebugInstantiate,
2897        input: Box<HydroNode>,
2898        metadata: HydroIrMetadata,
2899    },
2900
2901    VersionedNetworkFork {
2902        channel_id: u32,
2903        channel_name: String,
2904        senders: Vec<(u32, Box<HydroNode>, NetworkSend)>,
2905        metadata: HydroIrMetadata,
2906    },
2907
2908    VersionedNetwork {
2909        fork: SharedNode,
2910        version: u32,
2911        deserialize: NetworkRecv,
2912        metadata: HydroIrMetadata,
2913    },
2914
2915    ExternalInput {
2916        from_external_key: LocationKey,
2917        from_port_id: ExternalPortId,
2918        from_many: bool,
2919        codec_type: DebugType,
2920        #[serde(skip)]
2921        port_hint: NetworkHint,
2922        instantiate_fn: DebugInstantiate,
2923        deserialize_fn: Option<DebugExpr>,
2924        metadata: HydroIrMetadata,
2925    },
2926
2927    Counter {
2928        tag: String,
2929        duration: DebugExpr,
2930        prefix: String,
2931        input: Box<HydroNode>,
2932        metadata: HydroIrMetadata,
2933    },
2934
2935    AssertIsConsistent {
2936        inner: Box<HydroNode>,
2937        trusted: bool,
2938        metadata: HydroIrMetadata,
2939    },
2940
2941    UnboundSingleton {
2942        inner: Box<HydroNode>,
2943        metadata: HydroIrMetadata,
2944    },
2945}
2946
2947pub type SeenSharedNodes = HashMap<*const RefCell<HydroNode>, Rc<RefCell<HydroNode>>>;
2948pub type SeenSharedNodeLocations = HashMap<*const RefCell<HydroNode>, LocationId>;
2949
2950/// If `f` has a mut singleton ref and `in_kind` is non-strict, emits an
2951/// `observe_for_mut` node and returns the new ident. Otherwise returns
2952/// `in_ident` unchanged. Always consumes a stmt_id when applicable.
2953#[cfg(feature = "build")]
2954fn maybe_observe_for_mut(
2955    f: &ClosureExpr,
2956    in_ident: syn::Ident,
2957    in_location: &LocationId,
2958    in_kind: &CollectionKind,
2959    op_meta: &HydroIrOpMetadata,
2960    builders_or_callback: &mut BuildersOrCallback<
2961        impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
2962        impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
2963    >,
2964    next_stmt_id: &mut crate::Counter<StmtId>,
2965) -> syn::Ident {
2966    if f.has_mut_ref() && !in_kind.is_strict() {
2967        let observe_stmt_id = next_stmt_id.get_and_increment();
2968        let observe_ident =
2969            syn::Ident::new(&format!("stream_{}", observe_stmt_id), Span::call_site());
2970        if let BuildersOrCallback::Builders(graph_builders) = builders_or_callback {
2971            graph_builders.observe_for_mut(in_location, in_ident, in_kind, &observe_ident, op_meta);
2972        }
2973        observe_ident
2974    } else {
2975        in_ident
2976    }
2977}
2978
2979impl HydroNode {
2980    pub fn transform_bottom_up(
2981        &mut self,
2982        transform: &mut impl FnMut(&mut HydroNode),
2983        seen_tees: &mut SeenSharedNodes,
2984        check_well_formed: bool,
2985    ) {
2986        self.transform_children(
2987            |n, s| n.transform_bottom_up(transform, s, check_well_formed),
2988            seen_tees,
2989        );
2990
2991        transform(self);
2992
2993        let self_location = self.metadata().location_id.root();
2994
2995        if check_well_formed {
2996            match &*self {
2997                HydroNode::Network { .. } => {}
2998                _ => {
2999                    self.input_metadata().iter().for_each(|i| {
3000                        if i.location_id.root() != self_location {
3001                            panic!(
3002                                "Mismatching IR locations, child: {:?} ({:?}) of: {:?} ({:?})",
3003                                i,
3004                                i.location_id.root(),
3005                                self,
3006                                self_location
3007                            )
3008                        }
3009                    });
3010                }
3011            }
3012        }
3013    }
3014
3015    #[inline(always)]
3016    pub fn transform_children(
3017        &mut self,
3018        mut transform: impl FnMut(&mut HydroNode, &mut SeenSharedNodes),
3019        seen_tees: &mut SeenSharedNodes,
3020    ) {
3021        match self {
3022            HydroNode::Placeholder => {
3023                panic!();
3024            }
3025
3026            HydroNode::Source { .. }
3027            | HydroNode::SingletonSource { .. }
3028            | HydroNode::CycleSource { .. }
3029            | HydroNode::ExternalInput { .. } => {}
3030
3031            HydroNode::Tee { inner, .. } | HydroNode::Reference { inner, .. } => {
3032                if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
3033                    *inner = SharedNode(transformed.clone());
3034                } else {
3035                    let transformed_cell = Rc::new(RefCell::new(HydroNode::Placeholder));
3036                    seen_tees.insert(inner.as_ptr(), transformed_cell.clone());
3037                    let mut orig = inner.0.replace(HydroNode::Placeholder);
3038                    transform(&mut orig, seen_tees);
3039                    *transformed_cell.borrow_mut() = orig;
3040                    *inner = SharedNode(transformed_cell);
3041                }
3042            }
3043
3044            HydroNode::Partition { inner, f, .. } => {
3045                if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
3046                    *inner = SharedNode(transformed.clone());
3047                } else {
3048                    f.transform_children(&mut transform, seen_tees);
3049                    let transformed_cell = Rc::new(RefCell::new(HydroNode::Placeholder));
3050                    seen_tees.insert(inner.as_ptr(), transformed_cell.clone());
3051                    let mut orig = inner.0.replace(HydroNode::Placeholder);
3052                    transform(&mut orig, seen_tees);
3053                    *transformed_cell.borrow_mut() = orig;
3054                    *inner = SharedNode(transformed_cell);
3055                }
3056            }
3057
3058            HydroNode::Cast { inner, .. }
3059            | HydroNode::ObserveNonDet { inner, .. }
3060            | HydroNode::BeginAtomic { inner, .. }
3061            | HydroNode::EndAtomic { inner, .. }
3062            | HydroNode::Batch { inner, .. }
3063            | HydroNode::YieldConcat { inner, .. }
3064            | HydroNode::UnboundSingleton { inner, .. }
3065            | HydroNode::AssertIsConsistent { inner, .. } => {
3066                transform(inner.as_mut(), seen_tees);
3067            }
3068
3069            HydroNode::Chain { first, second, .. } => {
3070                transform(first.as_mut(), seen_tees);
3071                transform(second.as_mut(), seen_tees);
3072            }
3073
3074            HydroNode::MergeOrdered { first, second, .. } => {
3075                transform(first.as_mut(), seen_tees);
3076                transform(second.as_mut(), seen_tees);
3077            }
3078
3079            HydroNode::ChainFirst { first, second, .. } => {
3080                transform(first.as_mut(), seen_tees);
3081                transform(second.as_mut(), seen_tees);
3082            }
3083
3084            HydroNode::CrossSingleton { left, right, .. }
3085            | HydroNode::CrossProduct { left, right, .. }
3086            | HydroNode::Join { left, right, .. }
3087            | HydroNode::JoinHalf { left, right, .. } => {
3088                transform(left.as_mut(), seen_tees);
3089                transform(right.as_mut(), seen_tees);
3090            }
3091
3092            HydroNode::Difference { pos, neg, .. } | HydroNode::AntiJoin { pos, neg, .. } => {
3093                transform(pos.as_mut(), seen_tees);
3094                transform(neg.as_mut(), seen_tees);
3095            }
3096
3097            HydroNode::Map { f, input, .. } => {
3098                f.transform_children(&mut transform, seen_tees);
3099                transform(input.as_mut(), seen_tees);
3100            }
3101            HydroNode::FlatMap { f, input, .. }
3102            | HydroNode::FlatMapStreamBlocking { f, input, .. }
3103            | HydroNode::Filter { f, input, .. }
3104            | HydroNode::FilterMap { f, input, .. }
3105            | HydroNode::Inspect { f, input, .. }
3106            | HydroNode::Reduce { f, input, .. }
3107            | HydroNode::ReduceKeyed { f, input, .. } => {
3108                f.transform_children(&mut transform, seen_tees);
3109                transform(input.as_mut(), seen_tees);
3110            }
3111            HydroNode::ReduceKeyedWatermark {
3112                f,
3113                input,
3114                watermark,
3115                ..
3116            } => {
3117                f.transform_children(&mut transform, seen_tees);
3118                transform(input.as_mut(), seen_tees);
3119                transform(watermark.as_mut(), seen_tees);
3120            }
3121            HydroNode::Fold {
3122                init, acc, input, ..
3123            }
3124            | HydroNode::Scan {
3125                init, acc, input, ..
3126            }
3127            | HydroNode::ScanAsyncBlocking {
3128                init, acc, input, ..
3129            }
3130            | HydroNode::FoldKeyed {
3131                init, acc, input, ..
3132            } => {
3133                init.transform_children(&mut transform, seen_tees);
3134                acc.transform_children(&mut transform, seen_tees);
3135                transform(input.as_mut(), seen_tees);
3136            }
3137            HydroNode::ResolveFutures { input, .. }
3138            | HydroNode::ResolveFuturesBlocking { input, .. }
3139            | HydroNode::ResolveFuturesOrdered { input, .. }
3140            | HydroNode::Sort { input, .. }
3141            | HydroNode::DeferTick { input, .. }
3142            | HydroNode::Enumerate { input, .. }
3143            | HydroNode::Unique { input, .. }
3144            | HydroNode::Network { input, .. }
3145            | HydroNode::Counter { input, .. } => {
3146                transform(input.as_mut(), seen_tees);
3147            }
3148
3149            HydroNode::VersionedNetworkFork { senders, .. } => {
3150                for (_version, sender, _serialize) in senders.iter_mut() {
3151                    transform(sender.as_mut(), seen_tees);
3152                }
3153            }
3154
3155            HydroNode::VersionedNetwork { fork, .. } => {
3156                if let Some(transformed) = seen_tees.get(&fork.as_ptr()) {
3157                    *fork = SharedNode(transformed.clone());
3158                } else {
3159                    let transformed_cell = Rc::new(RefCell::new(HydroNode::Placeholder));
3160                    seen_tees.insert(fork.as_ptr(), transformed_cell.clone());
3161                    let mut orig = fork.0.replace(HydroNode::Placeholder);
3162                    transform(&mut orig, seen_tees);
3163                    *transformed_cell.borrow_mut() = orig;
3164                    *fork = SharedNode(transformed_cell);
3165                }
3166            }
3167        }
3168    }
3169
3170    pub fn deep_clone(&self, seen_tees: &mut SeenSharedNodes) -> HydroNode {
3171        match self {
3172            HydroNode::Placeholder => HydroNode::Placeholder,
3173            HydroNode::Cast { inner, metadata } => HydroNode::Cast {
3174                inner: Box::new(inner.deep_clone(seen_tees)),
3175                metadata: metadata.clone(),
3176            },
3177            HydroNode::UnboundSingleton { inner, metadata } => HydroNode::UnboundSingleton {
3178                inner: Box::new(inner.deep_clone(seen_tees)),
3179                metadata: metadata.clone(),
3180            },
3181            HydroNode::ObserveNonDet {
3182                inner,
3183                trusted,
3184                metadata,
3185            } => HydroNode::ObserveNonDet {
3186                inner: Box::new(inner.deep_clone(seen_tees)),
3187                trusted: *trusted,
3188                metadata: metadata.clone(),
3189            },
3190            HydroNode::AssertIsConsistent {
3191                inner,
3192                trusted,
3193                metadata,
3194            } => HydroNode::AssertIsConsistent {
3195                inner: Box::new(inner.deep_clone(seen_tees)),
3196                trusted: *trusted,
3197                metadata: metadata.clone(),
3198            },
3199            HydroNode::Source { source, metadata } => HydroNode::Source {
3200                source: source.clone(),
3201                metadata: metadata.clone(),
3202            },
3203            HydroNode::SingletonSource {
3204                value,
3205                first_tick_only,
3206                metadata,
3207            } => HydroNode::SingletonSource {
3208                value: value.clone(),
3209                first_tick_only: *first_tick_only,
3210                metadata: metadata.clone(),
3211            },
3212            HydroNode::CycleSource { cycle_id, metadata } => HydroNode::CycleSource {
3213                cycle_id: *cycle_id,
3214                metadata: metadata.clone(),
3215            },
3216            HydroNode::Tee { inner, metadata }
3217            | HydroNode::Reference {
3218                inner, metadata, ..
3219            } => {
3220                let cloned_inner = if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
3221                    SharedNode(transformed.clone())
3222                } else {
3223                    let new_rc = Rc::new(RefCell::new(HydroNode::Placeholder));
3224                    seen_tees.insert(inner.as_ptr(), new_rc.clone());
3225                    let cloned = inner.0.borrow().deep_clone(seen_tees);
3226                    *new_rc.borrow_mut() = cloned;
3227                    SharedNode(new_rc)
3228                };
3229                if let HydroNode::Reference {
3230                    kind,
3231                    access_counter,
3232                    ..
3233                } = self
3234                {
3235                    HydroNode::Reference {
3236                        inner: cloned_inner,
3237                        kind: *kind,
3238                        access_counter: access_counter.freeze(),
3239                        metadata: metadata.clone(),
3240                    }
3241                } else {
3242                    HydroNode::Tee {
3243                        inner: cloned_inner,
3244                        metadata: metadata.clone(),
3245                    }
3246                }
3247            }
3248            HydroNode::Partition {
3249                inner,
3250                f,
3251                is_true,
3252                metadata,
3253            } => {
3254                if let Some(transformed) = seen_tees.get(&inner.as_ptr()) {
3255                    HydroNode::Partition {
3256                        inner: SharedNode(transformed.clone()),
3257                        f: f.deep_clone(seen_tees),
3258                        is_true: *is_true,
3259                        metadata: metadata.clone(),
3260                    }
3261                } else {
3262                    let new_rc = Rc::new(RefCell::new(HydroNode::Placeholder));
3263                    seen_tees.insert(inner.as_ptr(), new_rc.clone());
3264                    let cloned = inner.0.borrow().deep_clone(seen_tees);
3265                    *new_rc.borrow_mut() = cloned;
3266                    HydroNode::Partition {
3267                        inner: SharedNode(new_rc),
3268                        f: f.deep_clone(seen_tees),
3269                        is_true: *is_true,
3270                        metadata: metadata.clone(),
3271                    }
3272                }
3273            }
3274            HydroNode::YieldConcat { inner, metadata } => HydroNode::YieldConcat {
3275                inner: Box::new(inner.deep_clone(seen_tees)),
3276                metadata: metadata.clone(),
3277            },
3278            HydroNode::BeginAtomic { inner, metadata } => HydroNode::BeginAtomic {
3279                inner: Box::new(inner.deep_clone(seen_tees)),
3280                metadata: metadata.clone(),
3281            },
3282            HydroNode::EndAtomic { inner, metadata } => HydroNode::EndAtomic {
3283                inner: Box::new(inner.deep_clone(seen_tees)),
3284                metadata: metadata.clone(),
3285            },
3286            HydroNode::Batch { inner, metadata } => HydroNode::Batch {
3287                inner: Box::new(inner.deep_clone(seen_tees)),
3288                metadata: metadata.clone(),
3289            },
3290            HydroNode::Chain {
3291                first,
3292                second,
3293                metadata,
3294            } => HydroNode::Chain {
3295                first: Box::new(first.deep_clone(seen_tees)),
3296                second: Box::new(second.deep_clone(seen_tees)),
3297                metadata: metadata.clone(),
3298            },
3299            HydroNode::MergeOrdered {
3300                first,
3301                second,
3302                metadata,
3303            } => HydroNode::MergeOrdered {
3304                first: Box::new(first.deep_clone(seen_tees)),
3305                second: Box::new(second.deep_clone(seen_tees)),
3306                metadata: metadata.clone(),
3307            },
3308            HydroNode::ChainFirst {
3309                first,
3310                second,
3311                metadata,
3312            } => HydroNode::ChainFirst {
3313                first: Box::new(first.deep_clone(seen_tees)),
3314                second: Box::new(second.deep_clone(seen_tees)),
3315                metadata: metadata.clone(),
3316            },
3317            HydroNode::CrossProduct {
3318                left,
3319                right,
3320                metadata,
3321            } => HydroNode::CrossProduct {
3322                left: Box::new(left.deep_clone(seen_tees)),
3323                right: Box::new(right.deep_clone(seen_tees)),
3324                metadata: metadata.clone(),
3325            },
3326            HydroNode::CrossSingleton {
3327                left,
3328                right,
3329                metadata,
3330            } => HydroNode::CrossSingleton {
3331                left: Box::new(left.deep_clone(seen_tees)),
3332                right: Box::new(right.deep_clone(seen_tees)),
3333                metadata: metadata.clone(),
3334            },
3335            HydroNode::Join {
3336                left,
3337                right,
3338                metadata,
3339            } => HydroNode::Join {
3340                left: Box::new(left.deep_clone(seen_tees)),
3341                right: Box::new(right.deep_clone(seen_tees)),
3342                metadata: metadata.clone(),
3343            },
3344            HydroNode::JoinHalf {
3345                left,
3346                right,
3347                metadata,
3348            } => HydroNode::JoinHalf {
3349                left: Box::new(left.deep_clone(seen_tees)),
3350                right: Box::new(right.deep_clone(seen_tees)),
3351                metadata: metadata.clone(),
3352            },
3353            HydroNode::Difference { pos, neg, metadata } => HydroNode::Difference {
3354                pos: Box::new(pos.deep_clone(seen_tees)),
3355                neg: Box::new(neg.deep_clone(seen_tees)),
3356                metadata: metadata.clone(),
3357            },
3358            HydroNode::AntiJoin { pos, neg, metadata } => HydroNode::AntiJoin {
3359                pos: Box::new(pos.deep_clone(seen_tees)),
3360                neg: Box::new(neg.deep_clone(seen_tees)),
3361                metadata: metadata.clone(),
3362            },
3363            HydroNode::ResolveFutures { input, metadata } => HydroNode::ResolveFutures {
3364                input: Box::new(input.deep_clone(seen_tees)),
3365                metadata: metadata.clone(),
3366            },
3367            HydroNode::ResolveFuturesBlocking { input, metadata } => {
3368                HydroNode::ResolveFuturesBlocking {
3369                    input: Box::new(input.deep_clone(seen_tees)),
3370                    metadata: metadata.clone(),
3371                }
3372            }
3373            HydroNode::ResolveFuturesOrdered { input, metadata } => {
3374                HydroNode::ResolveFuturesOrdered {
3375                    input: Box::new(input.deep_clone(seen_tees)),
3376                    metadata: metadata.clone(),
3377                }
3378            }
3379            HydroNode::Map { f, input, metadata } => HydroNode::Map {
3380                f: f.deep_clone(seen_tees),
3381                input: Box::new(input.deep_clone(seen_tees)),
3382                metadata: metadata.clone(),
3383            },
3384            HydroNode::FlatMap { f, input, metadata } => HydroNode::FlatMap {
3385                f: f.deep_clone(seen_tees),
3386                input: Box::new(input.deep_clone(seen_tees)),
3387                metadata: metadata.clone(),
3388            },
3389            HydroNode::FlatMapStreamBlocking { f, input, metadata } => {
3390                HydroNode::FlatMapStreamBlocking {
3391                    f: f.deep_clone(seen_tees),
3392                    input: Box::new(input.deep_clone(seen_tees)),
3393                    metadata: metadata.clone(),
3394                }
3395            }
3396            HydroNode::Filter { f, input, metadata } => HydroNode::Filter {
3397                f: f.deep_clone(seen_tees),
3398                input: Box::new(input.deep_clone(seen_tees)),
3399                metadata: metadata.clone(),
3400            },
3401            HydroNode::FilterMap { f, input, metadata } => HydroNode::FilterMap {
3402                f: f.deep_clone(seen_tees),
3403                input: Box::new(input.deep_clone(seen_tees)),
3404                metadata: metadata.clone(),
3405            },
3406            HydroNode::DeferTick { input, metadata } => HydroNode::DeferTick {
3407                input: Box::new(input.deep_clone(seen_tees)),
3408                metadata: metadata.clone(),
3409            },
3410            HydroNode::Enumerate { input, metadata } => HydroNode::Enumerate {
3411                input: Box::new(input.deep_clone(seen_tees)),
3412                metadata: metadata.clone(),
3413            },
3414            HydroNode::Inspect { f, input, metadata } => HydroNode::Inspect {
3415                f: f.deep_clone(seen_tees),
3416                input: Box::new(input.deep_clone(seen_tees)),
3417                metadata: metadata.clone(),
3418            },
3419            HydroNode::Unique { input, metadata } => HydroNode::Unique {
3420                input: Box::new(input.deep_clone(seen_tees)),
3421                metadata: metadata.clone(),
3422            },
3423            HydroNode::Sort { input, metadata } => HydroNode::Sort {
3424                input: Box::new(input.deep_clone(seen_tees)),
3425                metadata: metadata.clone(),
3426            },
3427            HydroNode::Fold {
3428                init,
3429                acc,
3430                input,
3431                metadata,
3432            } => HydroNode::Fold {
3433                init: init.deep_clone(seen_tees),
3434                acc: acc.deep_clone(seen_tees),
3435                input: Box::new(input.deep_clone(seen_tees)),
3436                metadata: metadata.clone(),
3437            },
3438            HydroNode::Scan {
3439                init,
3440                acc,
3441                input,
3442                metadata,
3443            } => HydroNode::Scan {
3444                init: init.deep_clone(seen_tees),
3445                acc: acc.deep_clone(seen_tees),
3446                input: Box::new(input.deep_clone(seen_tees)),
3447                metadata: metadata.clone(),
3448            },
3449            HydroNode::ScanAsyncBlocking {
3450                init,
3451                acc,
3452                input,
3453                metadata,
3454            } => HydroNode::ScanAsyncBlocking {
3455                init: init.deep_clone(seen_tees),
3456                acc: acc.deep_clone(seen_tees),
3457                input: Box::new(input.deep_clone(seen_tees)),
3458                metadata: metadata.clone(),
3459            },
3460            HydroNode::FoldKeyed {
3461                init,
3462                acc,
3463                input,
3464                metadata,
3465            } => HydroNode::FoldKeyed {
3466                init: init.deep_clone(seen_tees),
3467                acc: acc.deep_clone(seen_tees),
3468                input: Box::new(input.deep_clone(seen_tees)),
3469                metadata: metadata.clone(),
3470            },
3471            HydroNode::ReduceKeyedWatermark {
3472                f,
3473                input,
3474                watermark,
3475                metadata,
3476            } => HydroNode::ReduceKeyedWatermark {
3477                f: f.deep_clone(seen_tees),
3478                input: Box::new(input.deep_clone(seen_tees)),
3479                watermark: Box::new(watermark.deep_clone(seen_tees)),
3480                metadata: metadata.clone(),
3481            },
3482            HydroNode::Reduce { f, input, metadata } => HydroNode::Reduce {
3483                f: f.deep_clone(seen_tees),
3484                input: Box::new(input.deep_clone(seen_tees)),
3485                metadata: metadata.clone(),
3486            },
3487            HydroNode::ReduceKeyed { f, input, metadata } => HydroNode::ReduceKeyed {
3488                f: f.deep_clone(seen_tees),
3489                input: Box::new(input.deep_clone(seen_tees)),
3490                metadata: metadata.clone(),
3491            },
3492            HydroNode::Network {
3493                name,
3494                networking_info,
3495                serialize,
3496                deserialize,
3497                instantiate_fn,
3498                input,
3499                metadata,
3500            } => HydroNode::Network {
3501                name: name.clone(),
3502                networking_info: networking_info.clone(),
3503                serialize: serialize.clone(),
3504                deserialize: deserialize.clone(),
3505                instantiate_fn: instantiate_fn.clone(),
3506                input: Box::new(input.deep_clone(seen_tees)),
3507                metadata: metadata.clone(),
3508            },
3509            HydroNode::ExternalInput {
3510                from_external_key,
3511                from_port_id,
3512                from_many,
3513                codec_type,
3514                port_hint,
3515                instantiate_fn,
3516                deserialize_fn,
3517                metadata,
3518            } => HydroNode::ExternalInput {
3519                from_external_key: *from_external_key,
3520                from_port_id: *from_port_id,
3521                from_many: *from_many,
3522                codec_type: codec_type.clone(),
3523                port_hint: *port_hint,
3524                instantiate_fn: instantiate_fn.clone(),
3525                deserialize_fn: deserialize_fn.clone(),
3526                metadata: metadata.clone(),
3527            },
3528            HydroNode::Counter {
3529                tag,
3530                duration,
3531                prefix,
3532                input,
3533                metadata,
3534            } => HydroNode::Counter {
3535                tag: tag.clone(),
3536                duration: duration.clone(),
3537                prefix: prefix.clone(),
3538                input: Box::new(input.deep_clone(seen_tees)),
3539                metadata: metadata.clone(),
3540            },
3541            HydroNode::VersionedNetworkFork {
3542                channel_id,
3543                channel_name,
3544                senders,
3545                metadata,
3546            } => HydroNode::VersionedNetworkFork {
3547                channel_id: *channel_id,
3548                channel_name: channel_name.clone(),
3549                senders: senders
3550                    .iter()
3551                    .map(|(version, sender, serialize)| {
3552                        (
3553                            *version,
3554                            Box::new(sender.deep_clone(seen_tees)),
3555                            serialize.clone(),
3556                        )
3557                    })
3558                    .collect(),
3559                metadata: metadata.clone(),
3560            },
3561            HydroNode::VersionedNetwork {
3562                fork,
3563                version,
3564                deserialize,
3565                metadata,
3566            } => {
3567                let cloned_fork = if let Some(transformed) = seen_tees.get(&fork.as_ptr()) {
3568                    SharedNode(transformed.clone())
3569                } else {
3570                    let new_rc = Rc::new(RefCell::new(HydroNode::Placeholder));
3571                    seen_tees.insert(fork.as_ptr(), new_rc.clone());
3572                    let cloned = fork.0.borrow().deep_clone(seen_tees);
3573                    *new_rc.borrow_mut() = cloned;
3574                    SharedNode(new_rc)
3575                };
3576                HydroNode::VersionedNetwork {
3577                    fork: cloned_fork,
3578                    version: *version,
3579                    deserialize: deserialize.clone(),
3580                    metadata: metadata.clone(),
3581                }
3582            }
3583        }
3584    }
3585
3586    #[cfg(feature = "build")]
3587    pub fn emit_core(
3588        &mut self,
3589        builders_or_callback: &mut BuildersOrCallback<
3590            impl FnMut(&mut HydroRoot, &mut crate::Counter<StmtId>),
3591            impl FnMut(&mut HydroNode, &mut crate::Counter<StmtId>),
3592        >,
3593        seen_tees: &mut SeenSharedNodes,
3594        built_tees: &mut HashMap<*const RefCell<HydroNode>, Vec<syn::Ident>>,
3595        next_stmt_id: &mut crate::Counter<StmtId>,
3596        fold_hooked_idents: &mut HashSet<String>,
3597    ) -> syn::Ident {
3598        let mut ident_stack: Vec<syn::Ident> = Vec::new();
3599
3600        self.transform_bottom_up(
3601            &mut |node: &mut HydroNode| {
3602                let out_location = node.metadata().location_id.clone();
3603                match node {
3604                    HydroNode::Placeholder => {
3605                        panic!()
3606                    }
3607
3608                    HydroNode::Cast { .. } => {
3609                        // Cast passes through the input ident unchanged
3610                        // The input ident is already on the stack from processing the child
3611                        let _ = next_stmt_id.get_and_increment();
3612                        match builders_or_callback {
3613                            BuildersOrCallback::Builders(_) => {}
3614                            BuildersOrCallback::Callback(_, node_callback) => {
3615                                node_callback(node, next_stmt_id);
3616                            }
3617                        }
3618                        // input_ident stays on stack as output
3619                    }
3620
3621                    HydroNode::UnboundSingleton { .. } => {
3622                        let inner_ident = ident_stack.pop().unwrap();
3623
3624                        let stmt_id = next_stmt_id.get_and_increment();
3625                        let out_ident =
3626                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3627
3628                        match builders_or_callback {
3629                            BuildersOrCallback::Builders(graph_builders) => {
3630                                if graph_builders.singleton_intermediates() {
3631                                    graph_builders.add_dfir_at(
3632                                        &out_location,
3633                                        parse_quote! {
3634                                            #out_ident = #inner_ident;
3635                                        },
3636                                        None,
3637                                    );
3638                                } else {
3639                                    graph_builders.add_dfir_at(
3640                                        &out_location,
3641                                        parse_quote! {
3642                                            #out_ident = #inner_ident -> persist::<'static>();
3643                                        },
3644                                        None,
3645                                    );
3646                                }
3647                            }
3648                            BuildersOrCallback::Callback(_, node_callback) => {
3649                                node_callback(node, next_stmt_id);
3650                            }
3651                        }
3652
3653                        ident_stack.push(out_ident);
3654                    }
3655
3656                    HydroNode::AssertIsConsistent { inner, trusted, .. } => {
3657                        let inner_ident = ident_stack.pop().unwrap();
3658
3659                        let stmt_id = next_stmt_id.get_and_increment();
3660                        let out_ident =
3661                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3662
3663                        match builders_or_callback {
3664                            BuildersOrCallback::Builders(graph_builders) => {
3665                                graph_builders.assert_is_consistent(
3666                                    *trusted,
3667                                    &inner.metadata().location_id,
3668                                    inner_ident,
3669                                    &out_ident,
3670                                );
3671                            }
3672                            BuildersOrCallback::Callback(_, node_callback) => {
3673                                node_callback(node, next_stmt_id);
3674                            }
3675                        }
3676
3677                        ident_stack.push(out_ident);
3678                    }
3679
3680                    HydroNode::ObserveNonDet {
3681                        inner,
3682                        trusted,
3683                        metadata,
3684                        ..
3685                    } => {
3686                        let inner_ident = ident_stack.pop().unwrap();
3687
3688                        let stmt_id = next_stmt_id.get_and_increment();
3689                        let observe_ident =
3690                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3691
3692                        match builders_or_callback {
3693                            BuildersOrCallback::Builders(graph_builders) => {
3694                                graph_builders.observe_nondet(
3695                                    *trusted,
3696                                    &inner.metadata().location_id,
3697                                    inner_ident,
3698                                    &inner.metadata().collection_kind,
3699                                    &observe_ident,
3700                                    &metadata.collection_kind,
3701                                    &metadata.op,
3702                                );
3703                            }
3704                            BuildersOrCallback::Callback(_, node_callback) => {
3705                                node_callback(node, next_stmt_id);
3706                            }
3707                        }
3708
3709                        ident_stack.push(observe_ident);
3710                    }
3711
3712                    HydroNode::Batch {
3713                        inner, metadata, ..
3714                    } => {
3715                        let inner_ident = ident_stack.pop().unwrap();
3716
3717                        let stmt_id = next_stmt_id.get_and_increment();
3718                        let batch_ident =
3719                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3720
3721                        match builders_or_callback {
3722                            BuildersOrCallback::Builders(graph_builders) => {
3723                                graph_builders.batch(
3724                                    inner_ident,
3725                                    &inner.metadata().location_id,
3726                                    &inner.metadata().collection_kind,
3727                                    &batch_ident,
3728                                    &out_location,
3729                                    &metadata.op,
3730                                    fold_hooked_idents,
3731                                );
3732                            }
3733                            BuildersOrCallback::Callback(_, node_callback) => {
3734                                node_callback(node, next_stmt_id);
3735                            }
3736                        }
3737
3738                        ident_stack.push(batch_ident);
3739                    }
3740
3741                    HydroNode::YieldConcat { inner, .. } => {
3742                        let inner_ident = ident_stack.pop().unwrap();
3743
3744                        let stmt_id = next_stmt_id.get_and_increment();
3745                        let yield_ident =
3746                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3747
3748                        match builders_or_callback {
3749                            BuildersOrCallback::Builders(graph_builders) => {
3750                                graph_builders.yield_from_tick(
3751                                    inner_ident,
3752                                    &inner.metadata().location_id,
3753                                    &inner.metadata().collection_kind,
3754                                    &yield_ident,
3755                                    &out_location,
3756                                );
3757                            }
3758                            BuildersOrCallback::Callback(_, node_callback) => {
3759                                node_callback(node, next_stmt_id);
3760                            }
3761                        }
3762
3763                        ident_stack.push(yield_ident);
3764                    }
3765
3766                    HydroNode::BeginAtomic { inner, metadata } => {
3767                        let inner_ident = ident_stack.pop().unwrap();
3768
3769                        let stmt_id = next_stmt_id.get_and_increment();
3770                        let begin_ident =
3771                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3772
3773                        match builders_or_callback {
3774                            BuildersOrCallback::Builders(graph_builders) => {
3775                                graph_builders.begin_atomic(
3776                                    inner_ident,
3777                                    &inner.metadata().location_id,
3778                                    &inner.metadata().collection_kind,
3779                                    &begin_ident,
3780                                    &out_location,
3781                                    &metadata.op,
3782                                );
3783                            }
3784                            BuildersOrCallback::Callback(_, node_callback) => {
3785                                node_callback(node, next_stmt_id);
3786                            }
3787                        }
3788
3789                        ident_stack.push(begin_ident);
3790                    }
3791
3792                    HydroNode::EndAtomic { inner, .. } => {
3793                        let inner_ident = ident_stack.pop().unwrap();
3794
3795                        let stmt_id = next_stmt_id.get_and_increment();
3796                        let end_ident =
3797                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3798
3799                        match builders_or_callback {
3800                            BuildersOrCallback::Builders(graph_builders) => {
3801                                graph_builders.end_atomic(
3802                                    inner_ident,
3803                                    &inner.metadata().location_id,
3804                                    &inner.metadata().collection_kind,
3805                                    &end_ident,
3806                                );
3807                            }
3808                            BuildersOrCallback::Callback(_, node_callback) => {
3809                                node_callback(node, next_stmt_id);
3810                            }
3811                        }
3812
3813                        ident_stack.push(end_ident);
3814                    }
3815
3816                    HydroNode::Source {
3817                        source, metadata, ..
3818                    } => {
3819                        if let HydroSource::ExternalNetwork() = source {
3820                            ident_stack.push(syn::Ident::new("DUMMY", Span::call_site()));
3821                        } else {
3822                            let stmt_id = next_stmt_id.get_and_increment();
3823                            let source_ident =
3824                                syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3825
3826                            let source_stmt = match source {
3827                                HydroSource::Stream(expr) => {
3828                                    debug_assert!(metadata.location_id.is_top_level());
3829                                    parse_quote! {
3830                                        #source_ident = source_stream(#expr);
3831                                    }
3832                                }
3833
3834                                HydroSource::ExternalNetwork() => {
3835                                    unreachable!()
3836                                }
3837
3838                                HydroSource::Iter(expr) => {
3839                                    if metadata.location_id.is_top_level() {
3840                                        parse_quote! {
3841                                            #source_ident = source_iter(#expr);
3842                                        }
3843                                    } else {
3844                                        // TODO(shadaj): a more natural semantics would be to to re-evaluate the expression on each tick
3845                                        parse_quote! {
3846                                            #source_ident = source_iter(#expr) -> persist::<'static>();
3847                                        }
3848                                    }
3849                                }
3850
3851                                HydroSource::Spin() => {
3852                                    debug_assert!(metadata.location_id.is_top_level());
3853                                    parse_quote! {
3854                                        #source_ident = spin();
3855                                    }
3856                                }
3857
3858                                HydroSource::ClusterMembers(target_loc, state) => {
3859                                    debug_assert!(metadata.location_id.is_top_level());
3860
3861                                    let members_tee_ident = syn::Ident::new(
3862                                        &format!(
3863                                            "__cluster_members_tee_{}_{}",
3864                                            metadata.location_id.root().key(),
3865                                            target_loc.key(),
3866                                        ),
3867                                        Span::call_site(),
3868                                    );
3869
3870                                    match state {
3871                                        ClusterMembersState::Stream(d) => {
3872                                            parse_quote! {
3873                                                #members_tee_ident = source_stream(#d) -> tee();
3874                                                #source_ident = #members_tee_ident;
3875                                            }
3876                                        },
3877                                        ClusterMembersState::Uninit => syn::parse_quote! {
3878                                            #source_ident = source_stream(DUMMY);
3879                                        },
3880                                        ClusterMembersState::Tee(..) => parse_quote! {
3881                                            #source_ident = #members_tee_ident;
3882                                        },
3883                                    }
3884                                }
3885
3886                                HydroSource::Embedded(ident) => {
3887                                    parse_quote! {
3888                                        #source_ident = source_stream(#ident);
3889                                    }
3890                                }
3891
3892                                HydroSource::EmbeddedSingleton(ident) => {
3893                                    parse_quote! {
3894                                        #source_ident = source_iter([#ident]);
3895                                    }
3896                                }
3897                            };
3898
3899                            match builders_or_callback {
3900                                BuildersOrCallback::Builders(graph_builders) => {
3901                                    graph_builders.add_dfir_at(
3902                                        &out_location,
3903                                        source_stmt,
3904                                        Some(&stmt_id.to_string()),
3905                                    );
3906                                }
3907                                BuildersOrCallback::Callback(_, node_callback) => {
3908                                    node_callback(node, next_stmt_id);
3909                                }
3910                            }
3911
3912                            ident_stack.push(source_ident);
3913                        }
3914                    }
3915
3916                    HydroNode::SingletonSource { value, first_tick_only, metadata } => {
3917                        let stmt_id = next_stmt_id.get_and_increment();
3918                        let source_ident =
3919                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3920
3921                        match builders_or_callback {
3922                            BuildersOrCallback::Builders(graph_builders) => {
3923                                if *first_tick_only {
3924                                    assert!(
3925                                        !metadata.location_id.is_top_level(),
3926                                        "first_tick_only SingletonSource must be inside a tick"
3927                                    );
3928                                }
3929
3930                                if *first_tick_only
3931                                    || (metadata.location_id.is_top_level()
3932                                        && metadata.collection_kind.is_bounded())
3933                                {
3934                                    graph_builders.add_dfir_at(
3935                                        &out_location,
3936                                        parse_quote! {
3937                                            #source_ident = source_iter([#value]);
3938                                        },
3939                                        Some(&stmt_id.to_string()),
3940                                    );
3941                                } else {
3942                                    graph_builders.add_dfir_at(
3943                                        &out_location,
3944                                        parse_quote! {
3945                                            #source_ident = source_iter([#value]) -> persist::<'static>();
3946                                        },
3947                                        Some(&stmt_id.to_string()),
3948                                    );
3949                                }
3950                            }
3951                            BuildersOrCallback::Callback(_, node_callback) => {
3952                                node_callback(node, next_stmt_id);
3953                            }
3954                        }
3955
3956                        ident_stack.push(source_ident);
3957                    }
3958
3959                    HydroNode::CycleSource { cycle_id, .. } => {
3960                        let ident = cycle_id.as_ident();
3961
3962                        // consume a stmt id even though we did not emit anything so that we can instrument this
3963                        let _ = next_stmt_id.get_and_increment();
3964
3965                        match builders_or_callback {
3966                            BuildersOrCallback::Builders(_) => {}
3967                            BuildersOrCallback::Callback(_, node_callback) => {
3968                                node_callback(node, next_stmt_id);
3969                            }
3970                        }
3971
3972                        ident_stack.push(ident);
3973                    }
3974
3975                    HydroNode::Tee { inner, .. } => {
3976                        // we consume a stmt id regardless of if we emit the tee() operator,
3977                        // so that during rewrites we touch all recipients of the tee()
3978                        let stmt_id = next_stmt_id.get_and_increment();
3979
3980                        let ret_ident = if let Some(built_idents) =
3981                            built_tees.get(&(inner.0.as_ref() as *const RefCell<HydroNode>))
3982                        {
3983                            match builders_or_callback {
3984                                BuildersOrCallback::Builders(_) => {}
3985                                BuildersOrCallback::Callback(_, node_callback) => {
3986                                    node_callback(node, next_stmt_id);
3987                                }
3988                            }
3989
3990                            built_idents[0].clone()
3991                        } else {
3992                            // The inner node was already processed by transform_bottom_up,
3993                            // so its ident is on the stack
3994                            let inner_ident = ident_stack.pop().unwrap();
3995
3996                            let tee_ident =
3997                                syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
3998
3999                            built_tees.insert(
4000                                inner.0.as_ref() as *const RefCell<HydroNode>,
4001                                vec![tee_ident.clone()],
4002                            );
4003
4004                            match builders_or_callback {
4005                                BuildersOrCallback::Builders(graph_builders) => {
4006                                    // NOTE: With `forward_ref`, the fold codegen may not have
4007                                    // run yet when we reach this tee, so `fold_hooked_idents`
4008                                    // might not contain the inner ident. In that case we won't
4009                                    // propagate the "hooked" status to the tee and the
4010                                    // downstream singleton batch will use the normal
4011                                    // `SingletonHook` instead of `PassthroughSingletonHook`.
4012                                    // This is not a soundness issue: the fallback hook still
4013                                    // produces correct behavior, just with a redundant decision
4014                                    // point. TODO(https://github.com/hydro-project/hydro/issues/2856):
4015                                    // fix ordering so forward_ref folds are always processed
4016                                    // before their downstream tees.
4017                                    if fold_hooked_idents.contains(&inner_ident.to_string()) {
4018                                        fold_hooked_idents.insert(tee_ident.to_string());
4019                                    }
4020                                    graph_builders.add_dfir_at(
4021                                        &out_location,
4022                                        parse_quote! {
4023                                            #tee_ident = #inner_ident -> tee();
4024                                        },
4025                                        Some(&stmt_id.to_string()),
4026                                    );
4027                                }
4028                                BuildersOrCallback::Callback(_, node_callback) => {
4029                                    node_callback(node, next_stmt_id);
4030                                }
4031                            }
4032
4033                            tee_ident
4034                        };
4035
4036                        ident_stack.push(ret_ident);
4037                    }
4038
4039                    HydroNode::Reference { inner, kind, .. } => {
4040                        // we consume a stmt id regardless of if we emit the operator,
4041                        // so that during rewrites we touch all recipients
4042                        let stmt_id = next_stmt_id.get_and_increment();
4043
4044                        let ret_ident = if let Some(built_idents) =
4045                            built_tees.get(&(inner.0.as_ref() as *const RefCell<HydroNode>))
4046                        {
4047                            built_idents[0].clone()
4048                        } else {
4049                            let inner_ident = ident_stack.pop().unwrap();
4050
4051                            let ref_ident =
4052                                syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4053
4054                            built_tees.insert(
4055                                inner.0.as_ref() as *const RefCell<HydroNode>,
4056                                vec![ref_ident.clone()],
4057                            );
4058
4059                            match builders_or_callback {
4060                                BuildersOrCallback::Builders(graph_builders) => {
4061                                    let op_ident = syn::Ident::new(
4062                                        match kind {
4063                                            crate::handoff_ref::HandoffRefKind::Singleton => "singleton",
4064                                            crate::handoff_ref::HandoffRefKind::Optional => "optional",
4065                                            crate::handoff_ref::HandoffRefKind::Vec => "handoff",
4066                                        },
4067                                        Span::call_site(),
4068                                    );
4069                                    graph_builders.add_dfir_at(
4070                                        &out_location,
4071                                        parse_quote! {
4072                                            #ref_ident = #inner_ident -> #op_ident();
4073                                        },
4074                                        Some(&stmt_id.to_string()),
4075                                    );
4076                                }
4077                                BuildersOrCallback::Callback(_, node_callback) => {
4078                                    node_callback(node, next_stmt_id);
4079                                }
4080                            }
4081
4082                            ref_ident
4083                        };
4084
4085                        ident_stack.push(ret_ident);
4086                    }
4087
4088                    HydroNode::Partition {
4089                        inner, f, is_true, metadata,
4090                    } => {
4091                        let is_true = *is_true; // need to copy early to avoid borrow checking issues with node
4092                        let ptr = inner.0.as_ref() as *const RefCell<HydroNode>;
4093                        let stmt_id = next_stmt_id.get_and_increment();
4094
4095                        let ret_ident = if let Some(built_idents) = built_tees.get(&ptr) {
4096                            match builders_or_callback {
4097                                BuildersOrCallback::Builders(_) => {}
4098                                BuildersOrCallback::Callback(_, node_callback) => {
4099                                    node_callback(node, next_stmt_id);
4100                                }
4101                            }
4102
4103                            let idx = if is_true { 0 } else { 1 };
4104                            built_idents[idx].clone()
4105                        } else {
4106                            // The inner node was already processed by transform_bottom_up,
4107                            // so its ident is on the stack
4108                            let inner_ident = ident_stack.pop().unwrap();
4109                            let f_tokens = f.emit_tokens(&mut ident_stack);
4110
4111                            let inner_ident = {
4112                                let inner_borrow = inner.0.borrow();
4113                                maybe_observe_for_mut(
4114                                    f, inner_ident,
4115                                    &inner_borrow.metadata().location_id,
4116                                    &inner_borrow.metadata().collection_kind,
4117                                    &metadata.op,
4118                                    builders_or_callback, next_stmt_id,
4119                                )
4120                            };
4121
4122                            let partition_ident = syn::Ident::new(
4123                                &format!("stream_{}_partition", stmt_id),
4124                                Span::call_site(),
4125                            );
4126                            let true_ident = syn::Ident::new(
4127                                &format!("stream_{}_true", stmt_id),
4128                                Span::call_site(),
4129                            );
4130                            let false_ident = syn::Ident::new(
4131                                &format!("stream_{}_false", stmt_id),
4132                                Span::call_site(),
4133                            );
4134
4135                            built_tees.insert(
4136                                ptr,
4137                                vec![true_ident.clone(), false_ident.clone()],
4138                            );
4139
4140                            let stmt_id = next_stmt_id.get_and_increment();
4141                            match builders_or_callback {
4142                                BuildersOrCallback::Builders(graph_builders) => {
4143                                    graph_builders.add_dfir_at(
4144                                        &out_location,
4145                                        parse_quote! {
4146                                            #partition_ident = #inner_ident -> partition(|__item, __num_outputs| if (#f_tokens)(__item) { 0_usize } else { 1_usize });
4147                                            #true_ident = #partition_ident[0];
4148                                            #false_ident = #partition_ident[1];
4149                                        },
4150                                        Some(&stmt_id.to_string()),
4151                                    );
4152                                }
4153                                BuildersOrCallback::Callback(_, node_callback) => {
4154                                    node_callback(node, next_stmt_id);
4155                                }
4156                            }
4157
4158                            if is_true { true_ident } else { false_ident }
4159                        };
4160
4161                        ident_stack.push(ret_ident);
4162                    }
4163
4164                    HydroNode::Chain { .. } => {
4165                        // Children are processed left-to-right, so second is on top
4166                        let second_ident = ident_stack.pop().unwrap();
4167                        let first_ident = ident_stack.pop().unwrap();
4168
4169                        let stmt_id = next_stmt_id.get_and_increment();
4170                        let chain_ident =
4171                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4172
4173                        match builders_or_callback {
4174                            BuildersOrCallback::Builders(graph_builders) => {
4175                                graph_builders.add_dfir_at(
4176                                    &out_location,
4177                                    parse_quote! {
4178                                        #chain_ident = chain();
4179                                        #first_ident -> [0]#chain_ident;
4180                                        #second_ident -> [1]#chain_ident;
4181                                    },
4182                                    Some(&stmt_id.to_string()),
4183                                );
4184                            }
4185                            BuildersOrCallback::Callback(_, node_callback) => {
4186                                node_callback(node, next_stmt_id);
4187                            }
4188                        }
4189
4190                        ident_stack.push(chain_ident);
4191                    }
4192
4193                    HydroNode::MergeOrdered { first, metadata, .. } => {
4194                        let second_ident = ident_stack.pop().unwrap();
4195                        let first_ident = ident_stack.pop().unwrap();
4196
4197                        let stmt_id = next_stmt_id.get_and_increment();
4198                        let merge_ident =
4199                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4200
4201                        match builders_or_callback {
4202                            BuildersOrCallback::Builders(graph_builders) => {
4203                                graph_builders.merge_ordered(
4204                                    &first.metadata().location_id,
4205                                    first_ident,
4206                                    second_ident,
4207                                    &merge_ident,
4208                                    &first.metadata().collection_kind,
4209                                    &metadata.op,
4210                                    Some(&stmt_id.to_string()),
4211                                );
4212                            }
4213                            BuildersOrCallback::Callback(_, node_callback) => {
4214                                node_callback(node, next_stmt_id);
4215                            }
4216                        }
4217
4218                        ident_stack.push(merge_ident);
4219                    }
4220
4221                    HydroNode::ChainFirst { .. } => {
4222                        let second_ident = ident_stack.pop().unwrap();
4223                        let first_ident = ident_stack.pop().unwrap();
4224
4225                        let stmt_id = next_stmt_id.get_and_increment();
4226                        let chain_ident =
4227                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4228
4229                        match builders_or_callback {
4230                            BuildersOrCallback::Builders(graph_builders) => {
4231                                graph_builders.add_dfir_at(
4232                                    &out_location,
4233                                    parse_quote! {
4234                                        #chain_ident = chain_first_n(1);
4235                                        #first_ident -> [0]#chain_ident;
4236                                        #second_ident -> [1]#chain_ident;
4237                                    },
4238                                    Some(&stmt_id.to_string()),
4239                                );
4240                            }
4241                            BuildersOrCallback::Callback(_, node_callback) => {
4242                                node_callback(node, next_stmt_id);
4243                            }
4244                        }
4245
4246                        ident_stack.push(chain_ident);
4247                    }
4248
4249                    HydroNode::CrossSingleton { right, .. } => {
4250                        let right_ident = ident_stack.pop().unwrap();
4251                        let left_ident = ident_stack.pop().unwrap();
4252
4253                        let stmt_id = next_stmt_id.get_and_increment();
4254                        let cross_ident =
4255                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4256
4257                        match builders_or_callback {
4258                            BuildersOrCallback::Builders(graph_builders) => {
4259                                if right.metadata().location_id.is_top_level()
4260                                    && right.metadata().collection_kind.is_bounded()
4261                                {
4262                                    let lifetime =
4263                                        graph_builders.cross_tick_state_lifetime(&out_location);
4264                                    graph_builders.add_dfir_at(
4265                                        &out_location,
4266                                        parse_quote! {
4267                                            #cross_ident = cross_singleton::<#lifetime>();
4268                                            #left_ident -> [input]#cross_ident;
4269                                            #right_ident -> [single]#cross_ident;
4270                                        },
4271                                        Some(&stmt_id.to_string()),
4272                                    );
4273                                } else {
4274                                    graph_builders.add_dfir_at(
4275                                        &out_location,
4276                                        parse_quote! {
4277                                            #cross_ident = cross_singleton();
4278                                            #left_ident -> [input]#cross_ident;
4279                                            #right_ident -> [single]#cross_ident;
4280                                        },
4281                                        Some(&stmt_id.to_string()),
4282                                    );
4283                                }
4284                            }
4285                            BuildersOrCallback::Callback(_, node_callback) => {
4286                                node_callback(node, next_stmt_id);
4287                            }
4288                        }
4289
4290                        ident_stack.push(cross_ident);
4291                    }
4292
4293                    HydroNode::CrossProduct { .. } | HydroNode::Join { .. } => {
4294                        let operator: syn::Ident = if matches!(node, HydroNode::CrossProduct { .. }) {
4295                            parse_quote!(cross_join_multiset)
4296                        } else {
4297                            parse_quote!(join_multiset)
4298                        };
4299
4300                        let (HydroNode::CrossProduct { left, right, .. }
4301                        | HydroNode::Join { left, right, .. }) = node
4302                        else {
4303                            unreachable!()
4304                        };
4305
4306                        let is_top_level = left.metadata().location_id.is_top_level()
4307                            && right.metadata().location_id.is_top_level();
4308                        let left_top_level = left.metadata().location_id.is_top_level();
4309                        let right_top_level = right.metadata().location_id.is_top_level();
4310
4311                        let right_ident = ident_stack.pop().unwrap();
4312                        let left_ident = ident_stack.pop().unwrap();
4313
4314                        let stmt_id = next_stmt_id.get_and_increment();
4315                        let stream_ident =
4316                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4317
4318                        match builders_or_callback {
4319                            BuildersOrCallback::Builders(graph_builders) => {
4320                                let left_lifetime = if left_top_level {
4321                                    graph_builders.cross_tick_state_lifetime(&out_location)
4322                                } else {
4323                                    graph_builders.tick_state_lifetime(&out_location)
4324                                };
4325
4326                                let right_lifetime = if right_top_level {
4327                                    graph_builders.cross_tick_state_lifetime(&out_location)
4328                                } else {
4329                                    graph_builders.tick_state_lifetime(&out_location)
4330                                };
4331
4332                                graph_builders.add_dfir_at(
4333                                    &out_location,
4334                                    if is_top_level {
4335                                        // if both inputs are root, the output is expected to have streamy semantics, so we need
4336                                        // a multiset_delta() to negate the replay behavior
4337                                        parse_quote! {
4338                                            #stream_ident = #operator::<#left_lifetime, #right_lifetime>() -> multiset_delta();
4339                                            #left_ident -> [0]#stream_ident;
4340                                            #right_ident -> [1]#stream_ident;
4341                                        }
4342                                    } else {
4343                                        parse_quote! {
4344                                            #stream_ident = #operator::<#left_lifetime, #right_lifetime>();
4345                                            #left_ident -> [0]#stream_ident;
4346                                            #right_ident -> [1]#stream_ident;
4347                                        }
4348                                    },
4349                                    Some(&stmt_id.to_string()),
4350                                );
4351                            }
4352                            BuildersOrCallback::Callback(_, node_callback) => {
4353                                node_callback(node, next_stmt_id);
4354                            }
4355                        }
4356
4357                        ident_stack.push(stream_ident);
4358                    }
4359
4360                    HydroNode::Difference { .. } | HydroNode::AntiJoin { .. } => {
4361                        let operator: syn::Ident = if matches!(node, HydroNode::Difference { .. }) {
4362                            parse_quote!(difference)
4363                        } else {
4364                            parse_quote!(anti_join)
4365                        };
4366
4367                        let (HydroNode::Difference { neg, .. } | HydroNode::AntiJoin { neg, .. }) =
4368                            node
4369                        else {
4370                            unreachable!()
4371                        };
4372
4373                        let neg_top_level = neg.metadata().location_id.is_top_level();
4374
4375                        let neg_ident = ident_stack.pop().unwrap();
4376                        let pos_ident = ident_stack.pop().unwrap();
4377
4378                        let stmt_id = next_stmt_id.get_and_increment();
4379                        let stream_ident =
4380                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4381
4382                        match builders_or_callback {
4383                            BuildersOrCallback::Builders(graph_builders) => {
4384                                let neg_lifetime = if neg_top_level {
4385                                    graph_builders.cross_tick_state_lifetime(&out_location)
4386                                } else {
4387                                    graph_builders.tick_state_lifetime(&out_location)
4388                                };
4389                                let pos_lifetime =
4390                                    graph_builders.tick_state_lifetime(&out_location);
4391
4392                                graph_builders.add_dfir_at(
4393                                    &out_location,
4394                                    parse_quote! {
4395                                        #stream_ident = #operator::<#pos_lifetime, #neg_lifetime>();
4396                                        #pos_ident -> [pos]#stream_ident;
4397                                        #neg_ident -> [neg]#stream_ident;
4398                                    },
4399                                    Some(&stmt_id.to_string()),
4400                                );
4401                            }
4402                            BuildersOrCallback::Callback(_, node_callback) => {
4403                                node_callback(node, next_stmt_id);
4404                            }
4405                        }
4406
4407                        ident_stack.push(stream_ident);
4408                    }
4409
4410                    HydroNode::JoinHalf { .. } => {
4411                        let HydroNode::JoinHalf { right, .. } = node else {
4412                            unreachable!()
4413                        };
4414
4415                        assert!(
4416                            right.metadata().collection_kind.is_bounded(),
4417                            "JoinHalf requires the right (build) side to be Bounded, got {:?}",
4418                            right.metadata().collection_kind
4419                        );
4420
4421                        let build_top_level = right.metadata().location_id.is_top_level();
4422
4423                        let build_ident = ident_stack.pop().unwrap();
4424                        let probe_ident = ident_stack.pop().unwrap();
4425
4426                        let stmt_id = next_stmt_id.get_and_increment();
4427                        let stream_ident =
4428                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4429
4430                        match builders_or_callback {
4431                            BuildersOrCallback::Builders(graph_builders) => {
4432                                let build_lifetime = if build_top_level {
4433                                    graph_builders.cross_tick_state_lifetime(&out_location)
4434                                } else {
4435                                    graph_builders.tick_state_lifetime(&out_location)
4436                                };
4437                                let probe_lifetime =
4438                                    graph_builders.tick_state_lifetime(&out_location);
4439
4440                                graph_builders.add_dfir_at(
4441                                    &out_location,
4442                                    parse_quote! {
4443                                        #stream_ident = join_multiset_half::<#build_lifetime, #probe_lifetime>();
4444                                        #probe_ident -> [probe]#stream_ident;
4445                                        #build_ident -> [build]#stream_ident;
4446                                    },
4447                                    Some(&stmt_id.to_string()),
4448                                );
4449                            }
4450                            BuildersOrCallback::Callback(_, node_callback) => {
4451                                node_callback(node, next_stmt_id);
4452                            }
4453                        }
4454
4455                        ident_stack.push(stream_ident);
4456                    }
4457
4458                    HydroNode::ResolveFutures { .. } => {
4459                        let input_ident = ident_stack.pop().unwrap();
4460
4461                        let stmt_id = next_stmt_id.get_and_increment();
4462                        let futures_ident =
4463                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4464
4465                        match builders_or_callback {
4466                            BuildersOrCallback::Builders(graph_builders) => {
4467                                graph_builders.add_dfir_at(
4468                                    &out_location,
4469                                    parse_quote! {
4470                                        #futures_ident = #input_ident -> resolve_futures();
4471                                    },
4472                                    Some(&stmt_id.to_string()),
4473                                );
4474                            }
4475                            BuildersOrCallback::Callback(_, node_callback) => {
4476                                node_callback(node, next_stmt_id);
4477                            }
4478                        }
4479
4480                        ident_stack.push(futures_ident);
4481                    }
4482
4483                    HydroNode::ResolveFuturesBlocking { .. } => {
4484                        let input_ident = ident_stack.pop().unwrap();
4485
4486                        let stmt_id = next_stmt_id.get_and_increment();
4487                        let futures_ident =
4488                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4489
4490                        match builders_or_callback {
4491                            BuildersOrCallback::Builders(graph_builders) => {
4492                                graph_builders.add_dfir_at(
4493                                    &out_location,
4494                                    parse_quote! {
4495                                        #futures_ident = #input_ident -> resolve_futures_blocking();
4496                                    },
4497                                    Some(&stmt_id.to_string()),
4498                                );
4499                            }
4500                            BuildersOrCallback::Callback(_, node_callback) => {
4501                                node_callback(node, next_stmt_id);
4502                            }
4503                        }
4504
4505                        ident_stack.push(futures_ident);
4506                    }
4507
4508                    HydroNode::ResolveFuturesOrdered { .. } => {
4509                        let input_ident = ident_stack.pop().unwrap();
4510
4511                        let stmt_id = next_stmt_id.get_and_increment();
4512                        let futures_ident =
4513                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4514
4515                        match builders_or_callback {
4516                            BuildersOrCallback::Builders(graph_builders) => {
4517                                graph_builders.add_dfir_at(
4518                                    &out_location,
4519                                    parse_quote! {
4520                                        #futures_ident = #input_ident -> resolve_futures_ordered();
4521                                    },
4522                                    Some(&stmt_id.to_string()),
4523                                );
4524                            }
4525                            BuildersOrCallback::Callback(_, node_callback) => {
4526                                node_callback(node, next_stmt_id);
4527                            }
4528                        }
4529
4530                        ident_stack.push(futures_ident);
4531                    }
4532
4533                    HydroNode::Map {
4534                        f,
4535                        input,
4536                        metadata,
4537                    } => {
4538                        // Pop input ident (pushed last by transform_children).
4539                        let input_ident = ident_stack.pop().unwrap();
4540                        let f_tokens = f.emit_tokens(&mut ident_stack);
4541
4542                        let input_ident = maybe_observe_for_mut(
4543                            f,
4544                            input_ident,
4545                            &input.metadata().location_id,
4546                            &input.metadata().collection_kind,
4547                            &metadata.op,
4548                            builders_or_callback,
4549                            next_stmt_id,
4550                        );
4551
4552                        let stmt_id = next_stmt_id.get_and_increment();
4553                        let map_ident =
4554                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4555
4556                        match builders_or_callback {
4557                            BuildersOrCallback::Builders(graph_builders) => {
4558                                graph_builders.add_dfir_at(
4559                                    &out_location,
4560                                    parse_quote! {
4561                                        #map_ident = #input_ident -> map(#f_tokens);
4562                                    },
4563                                    Some(&stmt_id.to_string()),
4564                                );
4565                            }
4566                            BuildersOrCallback::Callback(_, node_callback) => {
4567                                node_callback(node, next_stmt_id);
4568                            }
4569                        }
4570
4571                        ident_stack.push(map_ident);
4572                    }
4573
4574                    HydroNode::FlatMap { f, input, metadata } => {
4575                        let input_ident = ident_stack.pop().unwrap();
4576                        let f_tokens = f.emit_tokens(&mut ident_stack);
4577
4578                        let input_ident = maybe_observe_for_mut(
4579                            f, input_ident,
4580                            &input.metadata().location_id,
4581                            &input.metadata().collection_kind,
4582                            &metadata.op,
4583                            builders_or_callback, next_stmt_id,
4584                        );
4585
4586                        let stmt_id = next_stmt_id.get_and_increment();
4587                        let flat_map_ident =
4588                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4589
4590                        match builders_or_callback {
4591                            BuildersOrCallback::Builders(graph_builders) => {
4592                                graph_builders.add_dfir_at(
4593                                    &out_location,
4594                                    parse_quote! {
4595                                        #flat_map_ident = #input_ident -> flat_map(#f_tokens);
4596                                    },
4597                                    Some(&stmt_id.to_string()),
4598                                );
4599                            }
4600                            BuildersOrCallback::Callback(_, node_callback) => {
4601                                node_callback(node, next_stmt_id);
4602                            }
4603                        }
4604
4605                        ident_stack.push(flat_map_ident);
4606                    }
4607
4608                    HydroNode::FlatMapStreamBlocking { f, input, metadata } => {
4609                        let input_ident = ident_stack.pop().unwrap();
4610                        let f_tokens = f.emit_tokens(&mut ident_stack);
4611
4612                        let input_ident = maybe_observe_for_mut(
4613                            f, input_ident,
4614                            &input.metadata().location_id,
4615                            &input.metadata().collection_kind,
4616                            &metadata.op,
4617                            builders_or_callback, next_stmt_id,
4618                        );
4619
4620                        let stmt_id = next_stmt_id.get_and_increment();
4621                        let flat_map_stream_blocking_ident =
4622                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4623
4624                        match builders_or_callback {
4625                            BuildersOrCallback::Builders(graph_builders) => {
4626                                graph_builders.add_dfir_at(
4627                                    &out_location,
4628                                    parse_quote! {
4629                                        #flat_map_stream_blocking_ident = #input_ident -> flat_map_stream_blocking(#f_tokens);
4630                                    },
4631                                    Some(&stmt_id.to_string()),
4632                                );
4633                            }
4634                            BuildersOrCallback::Callback(_, node_callback) => {
4635                                node_callback(node, next_stmt_id);
4636                            }
4637                        }
4638
4639                        ident_stack.push(flat_map_stream_blocking_ident);
4640                    }
4641
4642                    HydroNode::Filter { f, input, metadata } => {
4643                        let input_ident = ident_stack.pop().unwrap();
4644                        let f_tokens = f.emit_tokens(&mut ident_stack);
4645
4646                        let input_ident = maybe_observe_for_mut(
4647                            f, input_ident,
4648                            &input.metadata().location_id,
4649                            &input.metadata().collection_kind,
4650                            &metadata.op,
4651                            builders_or_callback, next_stmt_id,
4652                        );
4653
4654                        let stmt_id = next_stmt_id.get_and_increment();
4655                        let filter_ident =
4656                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4657
4658                        match builders_or_callback {
4659                            BuildersOrCallback::Builders(graph_builders) => {
4660                                graph_builders.add_dfir_at(
4661                                    &out_location,
4662                                    parse_quote! {
4663                                        #filter_ident = #input_ident -> filter(#f_tokens);
4664                                    },
4665                                    Some(&stmt_id.to_string()),
4666                                );
4667                            }
4668                            BuildersOrCallback::Callback(_, node_callback) => {
4669                                node_callback(node, next_stmt_id);
4670                            }
4671                        }
4672
4673                        ident_stack.push(filter_ident);
4674                    }
4675
4676                    HydroNode::FilterMap { f, input, metadata } => {
4677                        let input_ident = ident_stack.pop().unwrap();
4678                        let f_tokens = f.emit_tokens(&mut ident_stack);
4679
4680                        let input_ident = maybe_observe_for_mut(
4681                            f, input_ident,
4682                            &input.metadata().location_id,
4683                            &input.metadata().collection_kind,
4684                            &metadata.op,
4685                            builders_or_callback, next_stmt_id,
4686                        );
4687
4688                        let stmt_id = next_stmt_id.get_and_increment();
4689                        let filter_map_ident =
4690                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4691
4692                        match builders_or_callback {
4693                            BuildersOrCallback::Builders(graph_builders) => {
4694                                graph_builders.add_dfir_at(
4695                                    &out_location,
4696                                    parse_quote! {
4697                                        #filter_map_ident = #input_ident -> filter_map(#f_tokens);
4698                                    },
4699                                    Some(&stmt_id.to_string()),
4700                                );
4701                            }
4702                            BuildersOrCallback::Callback(_, node_callback) => {
4703                                node_callback(node, next_stmt_id);
4704                            }
4705                        }
4706
4707                        ident_stack.push(filter_map_ident);
4708                    }
4709
4710                    HydroNode::Sort { .. } => {
4711                        let input_ident = ident_stack.pop().unwrap();
4712
4713                        let stmt_id = next_stmt_id.get_and_increment();
4714                        let sort_ident =
4715                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4716
4717                        match builders_or_callback {
4718                            BuildersOrCallback::Builders(graph_builders) => {
4719                                graph_builders.add_dfir_at(
4720                                    &out_location,
4721                                    parse_quote! {
4722                                        #sort_ident = #input_ident -> sort();
4723                                    },
4724                                    Some(&stmt_id.to_string()),
4725                                );
4726                            }
4727                            BuildersOrCallback::Callback(_, node_callback) => {
4728                                node_callback(node, next_stmt_id);
4729                            }
4730                        }
4731
4732                        ident_stack.push(sort_ident);
4733                    }
4734
4735                    HydroNode::DeferTick { .. } => {
4736                        let input_ident = ident_stack.pop().unwrap();
4737
4738                        let stmt_id = next_stmt_id.get_and_increment();
4739                        let defer_tick_ident =
4740                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4741
4742                        match builders_or_callback {
4743                            BuildersOrCallback::Builders(graph_builders) => {
4744                                graph_builders.add_dfir_at(
4745                                    &out_location,
4746                                    parse_quote! {
4747                                        #defer_tick_ident = #input_ident -> defer_tick_lazy();
4748                                    },
4749                                    Some(&stmt_id.to_string()),
4750                                );
4751                            }
4752                            BuildersOrCallback::Callback(_, node_callback) => {
4753                                node_callback(node, next_stmt_id);
4754                            }
4755                        }
4756
4757                        ident_stack.push(defer_tick_ident);
4758                    }
4759
4760                    HydroNode::Enumerate { input, .. } => {
4761                        let input_ident = ident_stack.pop().unwrap();
4762
4763                        let stmt_id = next_stmt_id.get_and_increment();
4764                        let enumerate_ident =
4765                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4766
4767                        match builders_or_callback {
4768                            BuildersOrCallback::Builders(graph_builders) => {
4769                                let lifetime = if input.metadata().location_id.is_top_level() {
4770                                    graph_builders.cross_tick_state_lifetime(&out_location)
4771                                } else {
4772                                    graph_builders.tick_state_lifetime(&out_location)
4773                                };
4774                                graph_builders.add_dfir_at(
4775                                    &out_location,
4776                                    parse_quote! {
4777                                        #enumerate_ident = #input_ident -> enumerate::<#lifetime>();
4778                                    },
4779                                    Some(&stmt_id.to_string()),
4780                                );
4781                            }
4782                            BuildersOrCallback::Callback(_, node_callback) => {
4783                                node_callback(node, next_stmt_id);
4784                            }
4785                        }
4786
4787                        ident_stack.push(enumerate_ident);
4788                    }
4789
4790                    HydroNode::Inspect { f, input, metadata } => {
4791                        let input_ident = ident_stack.pop().unwrap();
4792                        let f_tokens = f.emit_tokens(&mut ident_stack);
4793
4794                        let input_ident = maybe_observe_for_mut(
4795                            f, input_ident,
4796                            &input.metadata().location_id,
4797                            &input.metadata().collection_kind,
4798                            &metadata.op,
4799                            builders_or_callback, next_stmt_id,
4800                        );
4801
4802                        let stmt_id = next_stmt_id.get_and_increment();
4803                        let inspect_ident =
4804                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4805
4806                        match builders_or_callback {
4807                            BuildersOrCallback::Builders(graph_builders) => {
4808                                graph_builders.add_dfir_at(
4809                                    &out_location,
4810                                    parse_quote! {
4811                                        #inspect_ident = #input_ident -> inspect(#f_tokens);
4812                                    },
4813                                    Some(&stmt_id.to_string()),
4814                                );
4815                            }
4816                            BuildersOrCallback::Callback(_, node_callback) => {
4817                                node_callback(node, next_stmt_id);
4818                            }
4819                        }
4820
4821                        ident_stack.push(inspect_ident);
4822                    }
4823
4824                    HydroNode::Unique { input, .. } => {
4825                        let input_ident = ident_stack.pop().unwrap();
4826
4827                        let stmt_id = next_stmt_id.get_and_increment();
4828                        let unique_ident =
4829                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4830
4831                        match builders_or_callback {
4832                            BuildersOrCallback::Builders(graph_builders) => {
4833                                let lifetime = if input.metadata().location_id.is_top_level() {
4834                                    graph_builders.cross_tick_state_lifetime(&out_location)
4835                                } else {
4836                                    graph_builders.tick_state_lifetime(&out_location)
4837                                };
4838
4839                                graph_builders.add_dfir_at(
4840                                    &out_location,
4841                                    parse_quote! {
4842                                        #unique_ident = #input_ident -> unique::<#lifetime>();
4843                                    },
4844                                    Some(&stmt_id.to_string()),
4845                                );
4846                            }
4847                            BuildersOrCallback::Callback(_, node_callback) => {
4848                                node_callback(node, next_stmt_id);
4849                            }
4850                        }
4851
4852                        ident_stack.push(unique_ident);
4853                    }
4854
4855                    HydroNode::Fold { .. } | HydroNode::FoldKeyed { .. } | HydroNode::Scan { .. } | HydroNode::ScanAsyncBlocking { .. } => {
4856                        let operator: syn::Ident = if let HydroNode::Fold { input, .. } = node {
4857                            if input.metadata().location_id.is_top_level()
4858                                && input.metadata().collection_kind.is_bounded()
4859                            {
4860                                parse_quote!(fold_no_replay)
4861                            } else {
4862                                parse_quote!(fold)
4863                            }
4864                        } else if matches!(node, HydroNode::Scan { .. }) {
4865                            parse_quote!(scan)
4866                        } else if matches!(node, HydroNode::ScanAsyncBlocking { .. }) {
4867                            parse_quote!(scan_async_blocking)
4868                        } else if let HydroNode::FoldKeyed { input, .. } = node {
4869                            if input.metadata().location_id.is_top_level()
4870                                && input.metadata().collection_kind.is_bounded()
4871                            {
4872                                todo!("Fold keyed on a top-level bounded collection is not yet supported")
4873                            } else {
4874                                parse_quote!(fold_keyed)
4875                            }
4876                        } else {
4877                            unreachable!()
4878                        };
4879
4880                        let (HydroNode::Fold { input, .. }
4881                        | HydroNode::FoldKeyed { input, .. }
4882                        | HydroNode::Scan { input, .. }
4883                        | HydroNode::ScanAsyncBlocking { input, .. }) = node
4884                        else {
4885                            unreachable!()
4886                        };
4887
4888                        let input_top_level = input.metadata().location_id.is_top_level();
4889
4890                        let input_ident = ident_stack.pop().unwrap();
4891
4892                        let (HydroNode::Fold { init, acc, .. }
4893                        | HydroNode::FoldKeyed { init, acc, .. }
4894                        | HydroNode::Scan { init, acc, .. }
4895                        | HydroNode::ScanAsyncBlocking { init, acc, .. }) = &*node
4896                        else {
4897                            unreachable!()
4898                        };
4899
4900                        let acc_tokens = acc.emit_tokens(&mut ident_stack);
4901                        let init_tokens = init.emit_tokens(&mut ident_stack);
4902
4903                        let stmt_id = next_stmt_id.get_and_increment();
4904                        let fold_ident =
4905                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
4906
4907                        match builders_or_callback {
4908                            BuildersOrCallback::Builders(graph_builders) => {
4909                                let lifetime = if input_top_level {
4910                                    graph_builders.cross_tick_state_lifetime(&out_location)
4911                                } else {
4912                                    graph_builders.tick_state_lifetime(&out_location)
4913                                };
4914
4915                                if matches!(node, HydroNode::Fold { .. })
4916                                    && node.metadata().location_id.is_top_level()
4917                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
4918                                    && graph_builders.singleton_intermediates()
4919                                    && !node.metadata().collection_kind.is_bounded()
4920                                {
4921                                    let HydroNode::Fold { input, .. } = &*node else { unreachable!() };
4922                                    let hooked_input_ident = graph_builders.emit_fold_hook(
4923                                        &input.metadata().location_id,
4924                                        &input_ident,
4925                                        &input.metadata().collection_kind,
4926                                        &node.metadata().op,
4927                                    );
4928
4929                                    let (effective_input, wrapped_acc) = if let Some(ref hooked) = hooked_input_ident {
4930                                        let acc: syn::Expr = parse_quote!({
4931                                            let mut __inner = #acc_tokens;
4932                                            move |__state, __batch: Vec<_>| {
4933                                                if __batch.is_empty() {
4934                                                    return None;
4935                                                }
4936                                                for __value in __batch {
4937                                                    __inner(__state, __value);
4938                                                }
4939                                                Some(__state.clone())
4940                                            }
4941                                        });
4942                                        (hooked, acc)
4943                                    } else {
4944                                        let acc: syn::Expr = parse_quote!({
4945                                            let mut __inner = #acc_tokens;
4946                                            move |__state, __value| {
4947                                                __inner(__state, __value);
4948                                                Some(__state.clone())
4949                                            }
4950                                        });
4951                                        (&input_ident, acc)
4952                                    };
4953
4954                                    graph_builders.add_dfir_at(
4955                                        &out_location,
4956                                        parse_quote! {
4957                                            source_iter([(#init_tokens)()]) -> [0]#fold_ident;
4958                                            #effective_input -> scan::<#lifetime>(#init_tokens, #wrapped_acc) -> [1]#fold_ident;
4959                                            #fold_ident = chain();
4960                                        },
4961                                        Some(&stmt_id.to_string()),
4962                                    );
4963
4964                                    if hooked_input_ident.is_some() {
4965                                        fold_hooked_idents.insert(fold_ident.to_string());
4966                                    }
4967                                } else if matches!(node, HydroNode::FoldKeyed { .. })
4968                                    && node.metadata().location_id.is_top_level()
4969                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
4970                                    && graph_builders.singleton_intermediates()
4971                                    && !node.metadata().collection_kind.is_bounded()
4972                                {
4973                                    let HydroNode::FoldKeyed { input, .. } = &*node else { unreachable!() };
4974                                    let hooked_input_ident = graph_builders.emit_fold_hook(
4975                                        &input.metadata().location_id,
4976                                        &input_ident,
4977                                        &input.metadata().collection_kind,
4978                                        &node.metadata().op,
4979                                    );
4980
4981                                    let wrapped_acc: syn::Expr = parse_quote!({
4982                                        let mut __init = #init_tokens;
4983                                        let mut __inner = #acc_tokens;
4984                                        move |__state, __kv: (_, _)| {
4985                                            // TODO(shadaj): we can avoid the clone when the entry exists
4986                                            let __state = __state
4987                                                .entry(::std::clone::Clone::clone(&__kv.0))
4988                                                .or_insert_with(|| (__init)());
4989                                            __inner(__state, __kv.1);
4990                                            Some((__kv.0, ::std::clone::Clone::clone(&*__state)))
4991                                        }
4992                                    });
4993
4994                                    if let Some(hooked_input_ident) = hooked_input_ident {
4995                                        graph_builders.add_dfir_at(
4996                                            &out_location,
4997                                            parse_quote! {
4998                                                #fold_ident = #hooked_input_ident -> flatten() -> scan::<#lifetime>(|| ::std::collections::HashMap::new(), #wrapped_acc);
4999                                            },
5000                                            Some(&stmt_id.to_string()),
5001                                        );
5002
5003                                        fold_hooked_idents.insert(fold_ident.to_string());
5004                                    } else {
5005                                        graph_builders.add_dfir_at(
5006                                            &out_location,
5007                                            parse_quote! {
5008                                                #fold_ident = #input_ident -> scan::<#lifetime>(|| ::std::collections::HashMap::new(), #wrapped_acc);
5009                                            },
5010                                            Some(&stmt_id.to_string()),
5011                                        );
5012                                    }
5013                                } else if (matches!(node, HydroNode::Fold { .. })
5014                                    || matches!(node, HydroNode::FoldKeyed { .. }))
5015                                    && !node.metadata().location_id.is_top_level()
5016                                    && graph_builders.singleton_intermediates()
5017                                {
5018                                    let input_ref = match &*node {
5019                                        HydroNode::Fold { input, .. } => input,
5020                                        HydroNode::FoldKeyed { input, .. } => input,
5021                                        _ => unreachable!(),
5022                                    };
5023                                    let hooked_input_ident = graph_builders.emit_fold_hook(
5024                                        &input_ref.metadata().location_id,
5025                                        &input_ident,
5026                                        &input_ref.metadata().collection_kind,
5027                                        &node.metadata().op,
5028                                    );
5029
5030                                    let actual_input = hooked_input_ident.as_ref().unwrap_or(&input_ident);
5031                                    graph_builders.add_dfir_at(
5032                                        &out_location,
5033                                        parse_quote! {
5034                                            #fold_ident = #actual_input -> #operator::<#lifetime>(#init_tokens, #acc_tokens);
5035                                        },
5036                                        Some(&stmt_id.to_string()),
5037                                    );
5038                                } else {
5039                                    graph_builders.add_dfir_at(
5040                                        &out_location,
5041                                        parse_quote! {
5042                                            #fold_ident = #input_ident -> #operator::<#lifetime>(#init_tokens, #acc_tokens);
5043                                        },
5044                                        Some(&stmt_id.to_string()),
5045                                    );
5046                                }
5047                            }
5048                            BuildersOrCallback::Callback(_, node_callback) => {
5049                                node_callback(node, next_stmt_id);
5050                            }
5051                        }
5052
5053                        ident_stack.push(fold_ident);
5054                    }
5055
5056                    HydroNode::Reduce { .. } | HydroNode::ReduceKeyed { .. } => {
5057                        let operator: syn::Ident = if let HydroNode::Reduce { input, .. } = node {
5058                            if input.metadata().location_id.is_top_level()
5059                                && input.metadata().collection_kind.is_bounded()
5060                            {
5061                                parse_quote!(reduce_no_replay)
5062                            } else {
5063                                parse_quote!(reduce)
5064                            }
5065                        } else if let HydroNode::ReduceKeyed { input, .. } = node {
5066                            if input.metadata().location_id.is_top_level()
5067                                && input.metadata().collection_kind.is_bounded()
5068                            {
5069                                todo!(
5070                                    "Calling keyed reduce on a top-level bounded collection is not supported"
5071                                )
5072                            } else {
5073                                parse_quote!(reduce_keyed)
5074                            }
5075                        } else {
5076                            unreachable!()
5077                        };
5078
5079                        let (HydroNode::Reduce { input, .. } | HydroNode::ReduceKeyed { input, .. }) = node
5080                        else {
5081                            unreachable!()
5082                        };
5083
5084                        let input_top_level = input.metadata().location_id.is_top_level();
5085
5086                        let input_ident = ident_stack.pop().unwrap();
5087
5088                        let (HydroNode::Reduce { f, .. } | HydroNode::ReduceKeyed { f, .. }) = &*node
5089                        else {
5090                            unreachable!()
5091                        };
5092
5093                        let f_tokens = f.emit_tokens(&mut ident_stack);
5094
5095                        let stmt_id = next_stmt_id.get_and_increment();
5096                        let reduce_ident =
5097                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5098
5099                        match builders_or_callback {
5100                            BuildersOrCallback::Builders(graph_builders) => {
5101                                let lifetime = if input_top_level {
5102                                    graph_builders.cross_tick_state_lifetime(&out_location)
5103                                } else {
5104                                    graph_builders.tick_state_lifetime(&out_location)
5105                                };
5106
5107                                if matches!(node, HydroNode::Reduce { .. })
5108                                    && node.metadata().location_id.is_top_level()
5109                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
5110                                    && graph_builders.singleton_intermediates()
5111                                    && !node.metadata().collection_kind.is_bounded()
5112                                {
5113                                    todo!(
5114                                        "Reduce with optional intermediates is not yet supported in simulator"
5115                                    );
5116                                } else if matches!(node, HydroNode::ReduceKeyed { .. })
5117                                    && node.metadata().location_id.is_top_level()
5118                                    && !(matches!(node.metadata().location_id, LocationId::Atomic(_)))
5119                                    && graph_builders.singleton_intermediates()
5120                                    && !node.metadata().collection_kind.is_bounded()
5121                                {
5122                                    todo!(
5123                                        "Reduce keyed with optional intermediates is not yet supported in simulator"
5124                                    );
5125                                } else {
5126                                    graph_builders.add_dfir_at(
5127                                        &out_location,
5128                                        parse_quote! {
5129                                            #reduce_ident = #input_ident -> #operator::<#lifetime>(#f_tokens);
5130                                        },
5131                                        Some(&stmt_id.to_string()),
5132                                    );
5133                                }
5134                            }
5135                            BuildersOrCallback::Callback(_, node_callback) => {
5136                                node_callback(node, next_stmt_id);
5137                            }
5138                        }
5139
5140                        ident_stack.push(reduce_ident);
5141                    }
5142
5143                    HydroNode::ReduceKeyedWatermark {
5144                        f,
5145                        input,
5146                        metadata,
5147                        ..
5148                    } => {
5149                        let input_top_level = input.metadata().location_id.is_top_level();
5150
5151                        // watermark is processed second, so it's on top
5152                        let watermark_ident = ident_stack.pop().unwrap();
5153                        let input_ident = ident_stack.pop().unwrap();
5154                        let f_tokens = f.emit_tokens(&mut ident_stack);
5155
5156                        let stmt_id = next_stmt_id.get_and_increment();
5157                        let chain_ident = syn::Ident::new(
5158                            &format!("reduce_keyed_watermark_chain_{}", stmt_id),
5159                            Span::call_site(),
5160                        );
5161
5162                        let fold_ident =
5163                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5164
5165                        let agg_operator: syn::Ident = if input.metadata().location_id.is_top_level()
5166                            && input.metadata().collection_kind.is_bounded()
5167                        {
5168                            parse_quote!(fold_no_replay)
5169                        } else {
5170                            parse_quote!(fold)
5171                        };
5172
5173                        match builders_or_callback {
5174                            BuildersOrCallback::Builders(graph_builders) => {
5175                                let lifetime = if input_top_level {
5176                                    graph_builders.cross_tick_state_lifetime(&out_location)
5177                                } else {
5178                                    graph_builders.tick_state_lifetime(&out_location)
5179                                };
5180
5181                                if metadata.location_id.is_top_level()
5182                                    && !(matches!(metadata.location_id, LocationId::Atomic(_)))
5183                                    && graph_builders.singleton_intermediates()
5184                                    && !metadata.collection_kind.is_bounded()
5185                                {
5186                                    todo!(
5187                                        "Reduce keyed watermarked on a top-level bounded collection is not yet supported"
5188                                    )
5189                                } else {
5190                                    graph_builders.add_dfir_at(
5191                                        &out_location,
5192                                        parse_quote! {
5193                                            #chain_ident = chain();
5194                                            #input_ident
5195                                                -> map(|x| (Some(x), None))
5196                                                -> [0]#chain_ident;
5197                                            #watermark_ident
5198                                                -> map(|watermark| (None, Some(watermark)))
5199                                                -> [1]#chain_ident;
5200
5201                                            #fold_ident = #chain_ident
5202                                                -> #agg_operator::<#lifetime>(|| (::std::collections::HashMap::new(), None), {
5203                                                    let __reduce_keyed_fn = #f_tokens;
5204                                                    move |(map, opt_curr_watermark), (opt_payload, opt_watermark)| {
5205                                                        if let Some((k, v)) = opt_payload {
5206                                                            if let Some(curr_watermark) = *opt_curr_watermark {
5207                                                                if k < curr_watermark {
5208                                                                    return;
5209                                                                }
5210                                                            }
5211                                                            match map.entry(k) {
5212                                                                ::std::collections::hash_map::Entry::Vacant(e) => {
5213                                                                    e.insert(v);
5214                                                                }
5215                                                                ::std::collections::hash_map::Entry::Occupied(mut e) => {
5216                                                                    __reduce_keyed_fn(e.get_mut(), v);
5217                                                                }
5218                                                            }
5219                                                        } else {
5220                                                            let watermark = opt_watermark.unwrap();
5221                                                            if let Some(curr_watermark) = *opt_curr_watermark {
5222                                                                if watermark <= curr_watermark {
5223                                                                    return;
5224                                                                }
5225                                                            }
5226                                                            map.retain(|k, _| *k >= watermark);
5227                                                            *opt_curr_watermark = Some(watermark);
5228                                                        }
5229                                                    }
5230                                                })
5231                                                -> flat_map(|(map, _curr_watermark)| map);
5232                                        },
5233                                        Some(&stmt_id.to_string()),
5234                                    );
5235                                }
5236                            }
5237                            BuildersOrCallback::Callback(_, node_callback) => {
5238                                node_callback(node, next_stmt_id);
5239                            }
5240                        }
5241
5242                        ident_stack.push(fold_ident);
5243                    }
5244
5245                    HydroNode::Network {
5246                        networking_info,
5247                        serialize,
5248                        deserialize,
5249                        instantiate_fn,
5250                        input,
5251                        ..
5252                    } => {
5253                        let input_ident = ident_stack.pop().unwrap();
5254
5255                        let stmt_id = next_stmt_id.get_and_increment();
5256                        let receiver_stream_ident =
5257                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5258
5259                        // For embedded (external) serialization, this synthesizes only the
5260                        // member-id tag conversions (if any) and passes the raw payload through.
5261                        let serialize_pipeline = serialize.pipeline();
5262                        let deserialize_pipeline = deserialize.pipeline();
5263
5264                        match builders_or_callback {
5265                            BuildersOrCallback::Builders(graph_builders) => {
5266                                let (sink_expr, source_expr) = match instantiate_fn {
5267                                    DebugInstantiate::Building => (
5268                                        syn::parse_quote!(DUMMY_SINK),
5269                                        syn::parse_quote!(DUMMY_SOURCE),
5270                                    ),
5271
5272                                    DebugInstantiate::Finalized(finalized) => {
5273                                        (finalized.sink.clone(), finalized.source.clone())
5274                                    }
5275                                };
5276
5277                                graph_builders.create_network(
5278                                    &input.metadata().location_id,
5279                                    &out_location,
5280                                    input_ident,
5281                                    &receiver_stream_ident,
5282                                    serialize_pipeline.as_ref(),
5283                                    sink_expr,
5284                                    source_expr,
5285                                    deserialize_pipeline.as_ref(),
5286                                    serialize.external_element_type(),
5287                                    stmt_id,
5288                                    networking_info,
5289                                );
5290                            }
5291                            BuildersOrCallback::Callback(_, node_callback) => {
5292                                node_callback(node, next_stmt_id);
5293                            }
5294                        }
5295
5296                        ident_stack.push(receiver_stream_ident);
5297                    }
5298
5299                    HydroNode::ExternalInput {
5300                        instantiate_fn,
5301                        deserialize_fn: deserialize_pipeline,
5302                        ..
5303                    } => {
5304                        let stmt_id = next_stmt_id.get_and_increment();
5305                        let receiver_stream_ident =
5306                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5307
5308                        match builders_or_callback {
5309                            BuildersOrCallback::Builders(graph_builders) => {
5310                                let (_, source_expr) = match instantiate_fn {
5311                                    DebugInstantiate::Building => (
5312                                        syn::parse_quote!(DUMMY_SINK),
5313                                        syn::parse_quote!(DUMMY_SOURCE),
5314                                    ),
5315
5316                                    DebugInstantiate::Finalized(finalized) => {
5317                                        (finalized.sink.clone(), finalized.source.clone())
5318                                    }
5319                                };
5320
5321                                graph_builders.create_external_source(
5322                                    &out_location,
5323                                    source_expr,
5324                                    &receiver_stream_ident,
5325                                    deserialize_pipeline.as_ref(),
5326                                    stmt_id,
5327                                );
5328                            }
5329                            BuildersOrCallback::Callback(_, node_callback) => {
5330                                node_callback(node, next_stmt_id);
5331                            }
5332                        }
5333
5334                        ident_stack.push(receiver_stream_ident);
5335                    }
5336
5337                    HydroNode::Counter {
5338                        tag,
5339                        duration,
5340                        prefix,
5341                        ..
5342                    } => {
5343                        let input_ident = ident_stack.pop().unwrap();
5344
5345                        let stmt_id = next_stmt_id.get_and_increment();
5346                        let counter_ident =
5347                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5348
5349                        match builders_or_callback {
5350                            BuildersOrCallback::Builders(graph_builders) => {
5351                                let arg = format!("{}({})", prefix, tag);
5352                                graph_builders.add_dfir_at(
5353                                    &out_location,
5354                                    parse_quote! {
5355                                        #counter_ident = #input_ident -> _counter(#arg, #duration);
5356                                    },
5357                                    Some(&stmt_id.to_string()),
5358                                );
5359                            }
5360                            BuildersOrCallback::Callback(_, node_callback) => {
5361                                node_callback(node, next_stmt_id);
5362                            }
5363                        }
5364
5365                        ident_stack.push(counter_ident);
5366                    }
5367
5368                    HydroNode::VersionedNetworkFork {
5369                        channel_id,
5370                        senders,
5371                        metadata,
5372                        ..
5373                    } => {
5374                        // sender idents are pushed in order of the 'senders' member.
5375                        let split_at = ident_stack.len() - senders.len();
5376                        let sender_idents = ident_stack.split_off(split_at);
5377
5378                        let stmt_id = next_stmt_id.get_and_increment();
5379
5380                        // All senders share the channel, so the raw element type (for embedded
5381                        // serialization) is read from the first sender.
5382                        let external_element_type =
5383                            senders.first().and_then(|(_, _, s)| s.external_element_type());
5384
5385                        match builders_or_callback {
5386                            BuildersOrCallback::Builders(graph_builders) => {
5387                                let sender_args: Vec<(LocationId, syn::Ident, Option<DebugExpr>)> =
5388                                    senders
5389                                        .iter()
5390                                        .zip(sender_idents)
5391                                        .map(|((_version, sender, serialize), ident)| {
5392                                            (
5393                                                sender.metadata().location_id.clone(),
5394                                                ident,
5395                                                serialize.pipeline(),
5396                                            )
5397                                        })
5398                                        .collect();
5399                                graph_builders.create_versioned_network_fork(
5400                                    *channel_id,
5401                                    &metadata.location_id,
5402                                    sender_args,
5403                                    external_element_type,
5404                                    stmt_id,
5405                                );
5406                            }
5407                            BuildersOrCallback::Callback(_, node_callback) => {
5408                                node_callback(node, next_stmt_id);
5409                            }
5410                        }
5411                    }
5412
5413                    HydroNode::VersionedNetwork {
5414                        fork,
5415                        deserialize,
5416                        metadata,
5417                        ..
5418                    } => {
5419                        let stmt_id = next_stmt_id.get_and_increment();
5420                        let receiver_stream_ident =
5421                            syn::Ident::new(&format!("stream_{}", stmt_id), Span::call_site());
5422
5423                        // The wire element type is determined by the channel's *source* kind, which
5424                        // all senders share; read it from the shared fork's first sender.
5425                        let (channel_id, source_loc) = {
5426                            let fork_ref = fork.0.borrow();
5427                            let HydroNode::VersionedNetworkFork {
5428                                channel_id,
5429                                senders,
5430                                ..
5431                            } = &*fork_ref
5432                            else {
5433                                unreachable!("VersionedNetwork.fork must be a VersionedNetworkFork");
5434                            };
5435                            let source_loc = senders
5436                                .first()
5437                                .map(|(_v, sender, _s)| sender.metadata().location_id.clone())
5438                                .expect("a VersionedNetworkFork always has at least one sender");
5439                            (*channel_id, source_loc)
5440                        };
5441
5442                        let deserialize_pipeline = deserialize.pipeline();
5443                        let external_element_type = deserialize.external_element_type();
5444
5445                        match builders_or_callback {
5446                            BuildersOrCallback::Builders(graph_builders) => {
5447                                graph_builders.create_versioned_network(
5448                                    channel_id,
5449                                    &source_loc,
5450                                    &metadata.location_id,
5451                                    &receiver_stream_ident,
5452                                    deserialize_pipeline.as_ref(),
5453                                    external_element_type,
5454                                    stmt_id,
5455                                );
5456                            }
5457                            BuildersOrCallback::Callback(_, node_callback) => {
5458                                node_callback(node, next_stmt_id);
5459                            }
5460                        }
5461
5462                        ident_stack.push(receiver_stream_ident);
5463                    }
5464                }
5465            },
5466            seen_tees,
5467            false,
5468        );
5469
5470        let ret = ident_stack
5471            .pop()
5472            .expect("ident_stack should have exactly one element after traversal");
5473        assert!(
5474            ident_stack.is_empty(),
5475            "ident_stack should be empty after popping the final ident, but has {} remaining element(s). \
5476             This indicates a bug in the code gen: some node pushed idents that were never consumed.",
5477            ident_stack.len()
5478        );
5479        ret
5480    }
5481
5482    pub fn visit_debug_expr(&mut self, mut transform: impl FnMut(&mut DebugExpr)) {
5483        match self {
5484            HydroNode::Placeholder => {
5485                panic!()
5486            }
5487            HydroNode::Cast { .. }
5488            | HydroNode::ObserveNonDet { .. }
5489            | HydroNode::UnboundSingleton { .. }
5490            | HydroNode::AssertIsConsistent { .. } => {}
5491            HydroNode::Source { source, .. } => match source {
5492                HydroSource::Stream(expr) | HydroSource::Iter(expr) => transform(expr),
5493                HydroSource::ExternalNetwork()
5494                | HydroSource::Spin()
5495                | HydroSource::ClusterMembers(_, _)
5496                | HydroSource::Embedded(_)
5497                | HydroSource::EmbeddedSingleton(_) => {} // TODO: what goes here?
5498            },
5499            HydroNode::SingletonSource { value, .. } => {
5500                transform(value);
5501            }
5502            HydroNode::CycleSource { .. }
5503            | HydroNode::Tee { .. }
5504            | HydroNode::Reference { .. }
5505            | HydroNode::YieldConcat { .. }
5506            | HydroNode::BeginAtomic { .. }
5507            | HydroNode::EndAtomic { .. }
5508            | HydroNode::Batch { .. }
5509            | HydroNode::Chain { .. }
5510            | HydroNode::MergeOrdered { .. }
5511            | HydroNode::ChainFirst { .. }
5512            | HydroNode::CrossProduct { .. }
5513            | HydroNode::CrossSingleton { .. }
5514            | HydroNode::ResolveFutures { .. }
5515            | HydroNode::ResolveFuturesBlocking { .. }
5516            | HydroNode::ResolveFuturesOrdered { .. }
5517            | HydroNode::Join { .. }
5518            | HydroNode::JoinHalf { .. }
5519            | HydroNode::Difference { .. }
5520            | HydroNode::AntiJoin { .. }
5521            | HydroNode::DeferTick { .. }
5522            | HydroNode::Enumerate { .. }
5523            | HydroNode::Unique { .. }
5524            | HydroNode::Sort { .. }
5525            | HydroNode::VersionedNetworkFork { .. }
5526            | HydroNode::VersionedNetwork { .. } => {}
5527            HydroNode::Map { f, .. }
5528            | HydroNode::FlatMap { f, .. }
5529            | HydroNode::FlatMapStreamBlocking { f, .. }
5530            | HydroNode::Filter { f, .. }
5531            | HydroNode::FilterMap { f, .. }
5532            | HydroNode::Inspect { f, .. }
5533            | HydroNode::Partition { f, .. }
5534            | HydroNode::Reduce { f, .. }
5535            | HydroNode::ReduceKeyed { f, .. }
5536            | HydroNode::ReduceKeyedWatermark { f, .. } => {
5537                transform(&mut f.expr);
5538            }
5539            HydroNode::Fold { init, acc, .. }
5540            | HydroNode::Scan { init, acc, .. }
5541            | HydroNode::ScanAsyncBlocking { init, acc, .. }
5542            | HydroNode::FoldKeyed { init, acc, .. } => {
5543                transform(&mut init.expr);
5544                transform(&mut acc.expr);
5545            }
5546            HydroNode::Network {
5547                serialize,
5548                deserialize,
5549                ..
5550            } => {
5551                if let NetworkSend::Custom {
5552                    serialize_fn: Some(serialize_fn),
5553                } = serialize
5554                {
5555                    transform(serialize_fn);
5556                }
5557                if let NetworkRecv::Custom {
5558                    deserialize_fn: Some(deserialize_fn),
5559                } = deserialize
5560                {
5561                    transform(deserialize_fn);
5562                }
5563            }
5564            HydroNode::ExternalInput { deserialize_fn, .. } => {
5565                if let Some(deserialize_fn) = deserialize_fn {
5566                    transform(deserialize_fn);
5567                }
5568            }
5569            HydroNode::Counter { duration, .. } => {
5570                transform(duration);
5571            }
5572        }
5573    }
5574
5575    pub fn op_metadata(&self) -> &HydroIrOpMetadata {
5576        &self.metadata().op
5577    }
5578
5579    pub fn metadata(&self) -> &HydroIrMetadata {
5580        match self {
5581            HydroNode::Placeholder => {
5582                panic!()
5583            }
5584            HydroNode::VersionedNetworkFork { metadata, .. }
5585            | HydroNode::VersionedNetwork { metadata, .. } => metadata,
5586            HydroNode::Cast { metadata, .. }
5587            | HydroNode::ObserveNonDet { metadata, .. }
5588            | HydroNode::AssertIsConsistent { metadata, .. }
5589            | HydroNode::UnboundSingleton { metadata, .. }
5590            | HydroNode::Source { metadata, .. }
5591            | HydroNode::SingletonSource { metadata, .. }
5592            | HydroNode::CycleSource { metadata, .. }
5593            | HydroNode::Tee { metadata, .. }
5594            | HydroNode::Reference { metadata, .. }
5595            | HydroNode::Partition { metadata, .. }
5596            | HydroNode::YieldConcat { metadata, .. }
5597            | HydroNode::BeginAtomic { metadata, .. }
5598            | HydroNode::EndAtomic { metadata, .. }
5599            | HydroNode::Batch { metadata, .. }
5600            | HydroNode::Chain { metadata, .. }
5601            | HydroNode::MergeOrdered { metadata, .. }
5602            | HydroNode::ChainFirst { metadata, .. }
5603            | HydroNode::CrossProduct { metadata, .. }
5604            | HydroNode::CrossSingleton { metadata, .. }
5605            | HydroNode::Join { metadata, .. }
5606            | HydroNode::JoinHalf { metadata, .. }
5607            | HydroNode::Difference { metadata, .. }
5608            | HydroNode::AntiJoin { metadata, .. }
5609            | HydroNode::ResolveFutures { metadata, .. }
5610            | HydroNode::ResolveFuturesBlocking { metadata, .. }
5611            | HydroNode::ResolveFuturesOrdered { metadata, .. }
5612            | HydroNode::Map { metadata, .. }
5613            | HydroNode::FlatMap { metadata, .. }
5614            | HydroNode::FlatMapStreamBlocking { metadata, .. }
5615            | HydroNode::Filter { metadata, .. }
5616            | HydroNode::FilterMap { metadata, .. }
5617            | HydroNode::DeferTick { metadata, .. }
5618            | HydroNode::Enumerate { metadata, .. }
5619            | HydroNode::Inspect { metadata, .. }
5620            | HydroNode::Unique { metadata, .. }
5621            | HydroNode::Sort { metadata, .. }
5622            | HydroNode::Scan { metadata, .. }
5623            | HydroNode::ScanAsyncBlocking { metadata, .. }
5624            | HydroNode::Fold { metadata, .. }
5625            | HydroNode::FoldKeyed { metadata, .. }
5626            | HydroNode::Reduce { metadata, .. }
5627            | HydroNode::ReduceKeyed { metadata, .. }
5628            | HydroNode::ReduceKeyedWatermark { metadata, .. }
5629            | HydroNode::ExternalInput { metadata, .. }
5630            | HydroNode::Network { metadata, .. }
5631            | HydroNode::Counter { metadata, .. } => metadata,
5632        }
5633    }
5634
5635    pub fn op_metadata_mut(&mut self) -> &mut HydroIrOpMetadata {
5636        &mut self.metadata_mut().op
5637    }
5638
5639    pub fn metadata_mut(&mut self) -> &mut HydroIrMetadata {
5640        match self {
5641            HydroNode::Placeholder => {
5642                panic!()
5643            }
5644            HydroNode::VersionedNetworkFork { metadata, .. }
5645            | HydroNode::VersionedNetwork { metadata, .. } => metadata,
5646            HydroNode::Cast { metadata, .. }
5647            | HydroNode::ObserveNonDet { metadata, .. }
5648            | HydroNode::AssertIsConsistent { metadata, .. }
5649            | HydroNode::UnboundSingleton { metadata, .. }
5650            | HydroNode::Source { metadata, .. }
5651            | HydroNode::SingletonSource { metadata, .. }
5652            | HydroNode::CycleSource { metadata, .. }
5653            | HydroNode::Tee { metadata, .. }
5654            | HydroNode::Reference { metadata, .. }
5655            | HydroNode::Partition { metadata, .. }
5656            | HydroNode::YieldConcat { metadata, .. }
5657            | HydroNode::BeginAtomic { metadata, .. }
5658            | HydroNode::EndAtomic { metadata, .. }
5659            | HydroNode::Batch { metadata, .. }
5660            | HydroNode::Chain { metadata, .. }
5661            | HydroNode::MergeOrdered { metadata, .. }
5662            | HydroNode::ChainFirst { metadata, .. }
5663            | HydroNode::CrossProduct { metadata, .. }
5664            | HydroNode::CrossSingleton { metadata, .. }
5665            | HydroNode::Join { metadata, .. }
5666            | HydroNode::JoinHalf { metadata, .. }
5667            | HydroNode::Difference { metadata, .. }
5668            | HydroNode::AntiJoin { metadata, .. }
5669            | HydroNode::ResolveFutures { metadata, .. }
5670            | HydroNode::ResolveFuturesBlocking { metadata, .. }
5671            | HydroNode::ResolveFuturesOrdered { metadata, .. }
5672            | HydroNode::Map { metadata, .. }
5673            | HydroNode::FlatMap { metadata, .. }
5674            | HydroNode::FlatMapStreamBlocking { metadata, .. }
5675            | HydroNode::Filter { metadata, .. }
5676            | HydroNode::FilterMap { metadata, .. }
5677            | HydroNode::DeferTick { metadata, .. }
5678            | HydroNode::Enumerate { metadata, .. }
5679            | HydroNode::Inspect { metadata, .. }
5680            | HydroNode::Unique { metadata, .. }
5681            | HydroNode::Sort { metadata, .. }
5682            | HydroNode::Scan { metadata, .. }
5683            | HydroNode::ScanAsyncBlocking { metadata, .. }
5684            | HydroNode::Fold { metadata, .. }
5685            | HydroNode::FoldKeyed { metadata, .. }
5686            | HydroNode::Reduce { metadata, .. }
5687            | HydroNode::ReduceKeyed { metadata, .. }
5688            | HydroNode::ReduceKeyedWatermark { metadata, .. }
5689            | HydroNode::ExternalInput { metadata, .. }
5690            | HydroNode::Network { metadata, .. }
5691            | HydroNode::Counter { metadata, .. } => metadata,
5692        }
5693    }
5694
5695    pub fn input(&self) -> Vec<&HydroNode> {
5696        match self {
5697            HydroNode::Placeholder => {
5698                panic!()
5699            }
5700            HydroNode::Source { .. }
5701            | HydroNode::SingletonSource { .. }
5702            | HydroNode::ExternalInput { .. }
5703            | HydroNode::CycleSource { .. }
5704            | HydroNode::Tee { .. }
5705            | HydroNode::Reference { .. }
5706            | HydroNode::Partition { .. }
5707            | HydroNode::VersionedNetwork { .. } => {
5708                // Tee/Partition/VersionedNetwork find their input in separate special ways
5709                vec![]
5710            }
5711            HydroNode::Cast { inner, .. }
5712            | HydroNode::ObserveNonDet { inner, .. }
5713            | HydroNode::YieldConcat { inner, .. }
5714            | HydroNode::BeginAtomic { inner, .. }
5715            | HydroNode::EndAtomic { inner, .. }
5716            | HydroNode::Batch { inner, .. }
5717            | HydroNode::UnboundSingleton { inner, .. }
5718            | HydroNode::AssertIsConsistent { inner, .. } => {
5719                vec![inner]
5720            }
5721            HydroNode::Chain { first, second, .. } => {
5722                vec![first, second]
5723            }
5724            HydroNode::MergeOrdered { first, second, .. } => {
5725                vec![first, second]
5726            }
5727            HydroNode::ChainFirst { first, second, .. } => {
5728                vec![first, second]
5729            }
5730            HydroNode::CrossProduct { left, right, .. }
5731            | HydroNode::CrossSingleton { left, right, .. }
5732            | HydroNode::Join { left, right, .. }
5733            | HydroNode::JoinHalf { left, right, .. } => {
5734                vec![left, right]
5735            }
5736            HydroNode::Difference { pos, neg, .. } | HydroNode::AntiJoin { pos, neg, .. } => {
5737                vec![pos, neg]
5738            }
5739            HydroNode::Map { input, .. }
5740            | HydroNode::FlatMap { input, .. }
5741            | HydroNode::FlatMapStreamBlocking { input, .. }
5742            | HydroNode::Filter { input, .. }
5743            | HydroNode::FilterMap { input, .. }
5744            | HydroNode::Sort { input, .. }
5745            | HydroNode::DeferTick { input, .. }
5746            | HydroNode::Enumerate { input, .. }
5747            | HydroNode::Inspect { input, .. }
5748            | HydroNode::Unique { input, .. }
5749            | HydroNode::Network { input, .. }
5750            | HydroNode::Counter { input, .. }
5751            | HydroNode::ResolveFutures { input, .. }
5752            | HydroNode::ResolveFuturesBlocking { input, .. }
5753            | HydroNode::ResolveFuturesOrdered { input, .. }
5754            | HydroNode::Fold { input, .. }
5755            | HydroNode::FoldKeyed { input, .. }
5756            | HydroNode::Reduce { input, .. }
5757            | HydroNode::ReduceKeyed { input, .. }
5758            | HydroNode::Scan { input, .. }
5759            | HydroNode::ScanAsyncBlocking { input, .. } => {
5760                vec![input]
5761            }
5762            HydroNode::ReduceKeyedWatermark {
5763                input, watermark, ..
5764            } => {
5765                vec![input, watermark]
5766            }
5767            HydroNode::VersionedNetworkFork { senders, .. } => senders
5768                .iter()
5769                .map(|(_version, sender, _serialize)| sender.as_ref())
5770                .collect(),
5771        }
5772    }
5773
5774    pub fn input_metadata(&self) -> Vec<&HydroIrMetadata> {
5775        self.input()
5776            .iter()
5777            .map(|input_node| input_node.metadata())
5778            .collect()
5779    }
5780
5781    /// Returns `true` if this node is a Tee or Partition whose inner Rc
5782    /// has other live references, meaning the upstream is already driven
5783    /// by another consumer and does not need a Null sink.
5784    pub fn is_shared_with_others(&self) -> bool {
5785        match self {
5786            HydroNode::Tee { inner, .. } | HydroNode::Partition { inner, .. } => {
5787                Rc::strong_count(&inner.0) > 1
5788            }
5789            // A zero-output reference node is valid in DFIR (it drains itself at
5790            // end of tick), so it doesn't need to be driven by another consumer.
5791            HydroNode::Reference { .. } => false,
5792            _ => false,
5793        }
5794    }
5795
5796    pub fn print_root(&self) -> String {
5797        match self {
5798            HydroNode::Placeholder => {
5799                panic!()
5800            }
5801            HydroNode::Cast { .. } => "Cast()".to_owned(),
5802            HydroNode::UnboundSingleton { .. } => "UnboundSingleton()".to_owned(),
5803            HydroNode::ObserveNonDet { .. } => "ObserveNonDet()".to_owned(),
5804            HydroNode::AssertIsConsistent { .. } => "AssertIsConsistent()".to_owned(),
5805            HydroNode::Source { source, .. } => format!("Source({:?})", source),
5806            HydroNode::SingletonSource {
5807                value,
5808                first_tick_only,
5809                ..
5810            } => format!(
5811                "SingletonSource({:?}, first_tick_only={})",
5812                value, first_tick_only
5813            ),
5814            HydroNode::CycleSource { cycle_id, .. } => format!("CycleSource({})", cycle_id),
5815            HydroNode::Tee { inner, .. } => {
5816                format!("Tee({})", inner.0.borrow().print_root())
5817            }
5818            HydroNode::Reference { inner, kind, .. } => {
5819                format!("Reference({:?}, {})", kind, inner.0.borrow().print_root())
5820            }
5821            HydroNode::Partition { f, is_true, .. } => {
5822                format!("Partition({:?}, is_true={})", f, is_true)
5823            }
5824            HydroNode::YieldConcat { .. } => "YieldConcat()".to_owned(),
5825            HydroNode::BeginAtomic { .. } => "BeginAtomic()".to_owned(),
5826            HydroNode::EndAtomic { .. } => "EndAtomic()".to_owned(),
5827            HydroNode::Batch { .. } => "Batch()".to_owned(),
5828            HydroNode::Chain { first, second, .. } => {
5829                format!("Chain({}, {})", first.print_root(), second.print_root())
5830            }
5831            HydroNode::MergeOrdered { first, second, .. } => {
5832                format!(
5833                    "MergeOrdered({}, {})",
5834                    first.print_root(),
5835                    second.print_root()
5836                )
5837            }
5838            HydroNode::ChainFirst { first, second, .. } => {
5839                format!(
5840                    "ChainFirst({}, {})",
5841                    first.print_root(),
5842                    second.print_root()
5843                )
5844            }
5845            HydroNode::CrossProduct { left, right, .. } => {
5846                format!(
5847                    "CrossProduct({}, {})",
5848                    left.print_root(),
5849                    right.print_root()
5850                )
5851            }
5852            HydroNode::CrossSingleton { left, right, .. } => {
5853                format!(
5854                    "CrossSingleton({}, {})",
5855                    left.print_root(),
5856                    right.print_root()
5857                )
5858            }
5859            HydroNode::Join { left, right, .. } => {
5860                format!("Join({}, {})", left.print_root(), right.print_root())
5861            }
5862            HydroNode::JoinHalf { left, right, .. } => {
5863                format!("JoinHalf({}, {})", left.print_root(), right.print_root())
5864            }
5865            HydroNode::Difference { pos, neg, .. } => {
5866                format!("Difference({}, {})", pos.print_root(), neg.print_root())
5867            }
5868            HydroNode::AntiJoin { pos, neg, .. } => {
5869                format!("AntiJoin({}, {})", pos.print_root(), neg.print_root())
5870            }
5871            HydroNode::ResolveFutures { .. } => "ResolveFutures()".to_owned(),
5872            HydroNode::ResolveFuturesBlocking { .. } => "ResolveFuturesBlocking()".to_owned(),
5873            HydroNode::ResolveFuturesOrdered { .. } => "ResolveFuturesOrdered()".to_owned(),
5874            HydroNode::Map { f, .. } => format!("Map({:?})", f),
5875            HydroNode::FlatMap { f, .. } => format!("FlatMap({:?})", f),
5876            HydroNode::FlatMapStreamBlocking { f, .. } => format!("FlatMapStreamBlocking({:?})", f),
5877            HydroNode::Filter { f, .. } => format!("Filter({:?})", f),
5878            HydroNode::FilterMap { f, .. } => format!("FilterMap({:?})", f),
5879            HydroNode::DeferTick { .. } => "DeferTick()".to_owned(),
5880            HydroNode::Enumerate { .. } => "Enumerate()".to_owned(),
5881            HydroNode::Inspect { f, .. } => format!("Inspect({:?})", f),
5882            HydroNode::Unique { .. } => "Unique()".to_owned(),
5883            HydroNode::Sort { .. } => "Sort()".to_owned(),
5884            HydroNode::Fold { init, acc, .. } => format!("Fold({:?}, {:?})", init, acc),
5885            HydroNode::Scan { init, acc, .. } => format!("Scan({:?}, {:?})", init, acc),
5886            HydroNode::ScanAsyncBlocking { init, acc, .. } => {
5887                format!("ScanAsyncBlocking({:?}, {:?})", init, acc)
5888            }
5889            HydroNode::FoldKeyed { init, acc, .. } => format!("FoldKeyed({:?}, {:?})", init, acc),
5890            HydroNode::Reduce { f, .. } => format!("Reduce({:?})", f),
5891            HydroNode::ReduceKeyed { f, .. } => format!("ReduceKeyed({:?})", f),
5892            HydroNode::ReduceKeyedWatermark { f, .. } => format!("ReduceKeyedWatermark({:?})", f),
5893            HydroNode::Network { .. } => "Network()".to_owned(),
5894            HydroNode::ExternalInput { .. } => "ExternalInput()".to_owned(),
5895            HydroNode::Counter { tag, duration, .. } => {
5896                format!("Counter({:?}, {:?})", tag, duration)
5897            }
5898            HydroNode::VersionedNetworkFork {
5899                channel_name,
5900                senders,
5901                ..
5902            } => {
5903                let versions: Vec<u32> = senders.iter().map(|(v, _, _)| *v).collect();
5904                format!(
5905                    "VersionedNetworkFork({}, senders={:?})",
5906                    channel_name, versions
5907                )
5908            }
5909            HydroNode::VersionedNetwork { version, .. } => {
5910                format!("VersionedNetwork(v{})", version)
5911            }
5912        }
5913    }
5914}
5915
5916#[cfg(feature = "build")]
5917#[expect(clippy::too_many_arguments, reason = "networking codegen")]
5918fn instantiate_network<'a, D>(
5919    env: &mut D::InstantiateEnv,
5920    from_location: &LocationId,
5921    to_location: &LocationId,
5922    processes: &SparseSecondaryMap<LocationKey, D::Process>,
5923    clusters: &SparseSecondaryMap<LocationKey, D::Cluster>,
5924    name: Option<&str>,
5925    networking_info: &crate::networking::NetworkingInfo,
5926    external_types: Option<(&syn::Type, &syn::Type)>,
5927) -> (syn::Expr, syn::Expr, Box<dyn FnOnce()>)
5928where
5929    D: Deploy<'a>,
5930{
5931    if external_types.is_some() && !D::SUPPORTS_EXTERNAL_SERIALIZATION {
5932        panic!(
5933            "`.embedded()` serialization leaves serialization to code outside of Hydro and is \
5934             only supported by the embedded deployment backend. Use `.bincode()` (or another \
5935             supported serialization backend) for this deployment target instead."
5936        );
5937    }
5938
5939    let ((sink, source), connect_fn) = match (from_location, to_location) {
5940        (&LocationId::Process(from), &LocationId::Process(to)) => {
5941            let from_node = processes
5942                .get(from)
5943                .unwrap_or_else(|| {
5944                    panic!("A process used in the graph was not instantiated: {}", from)
5945                })
5946                .clone();
5947            let to_node = processes
5948                .get(to)
5949                .unwrap_or_else(|| {
5950                    panic!("A process used in the graph was not instantiated: {}", to)
5951                })
5952                .clone();
5953
5954            let sink_port = from_node.next_port();
5955            let source_port = to_node.next_port();
5956
5957            (
5958                D::o2o_sink_source(
5959                    env,
5960                    &from_node,
5961                    &sink_port,
5962                    &to_node,
5963                    &source_port,
5964                    name,
5965                    networking_info,
5966                    external_types,
5967                ),
5968                D::o2o_connect(&from_node, &sink_port, &to_node, &source_port),
5969            )
5970        }
5971        (&LocationId::Process(from), &LocationId::Cluster(to)) => {
5972            let from_node = processes
5973                .get(from)
5974                .unwrap_or_else(|| {
5975                    panic!("A process used in the graph was not instantiated: {}", from)
5976                })
5977                .clone();
5978            let to_node = clusters
5979                .get(to)
5980                .unwrap_or_else(|| {
5981                    panic!("A cluster used in the graph was not instantiated: {}", to)
5982                })
5983                .clone();
5984
5985            let sink_port = from_node.next_port();
5986            let source_port = to_node.next_port();
5987
5988            (
5989                D::o2m_sink_source(
5990                    env,
5991                    &from_node,
5992                    &sink_port,
5993                    &to_node,
5994                    &source_port,
5995                    name,
5996                    networking_info,
5997                    external_types,
5998                ),
5999                D::o2m_connect(&from_node, &sink_port, &to_node, &source_port),
6000            )
6001        }
6002        (&LocationId::Cluster(from), &LocationId::Process(to)) => {
6003            let from_node = clusters
6004                .get(from)
6005                .unwrap_or_else(|| {
6006                    panic!("A cluster used in the graph was not instantiated: {}", from)
6007                })
6008                .clone();
6009            let to_node = processes
6010                .get(to)
6011                .unwrap_or_else(|| {
6012                    panic!("A process used in the graph was not instantiated: {}", to)
6013                })
6014                .clone();
6015
6016            let sink_port = from_node.next_port();
6017            let source_port = to_node.next_port();
6018
6019            (
6020                D::m2o_sink_source(
6021                    env,
6022                    &from_node,
6023                    &sink_port,
6024                    &to_node,
6025                    &source_port,
6026                    name,
6027                    networking_info,
6028                    external_types,
6029                ),
6030                D::m2o_connect(&from_node, &sink_port, &to_node, &source_port),
6031            )
6032        }
6033        (&LocationId::Cluster(from), &LocationId::Cluster(to)) => {
6034            let from_node = clusters
6035                .get(from)
6036                .unwrap_or_else(|| {
6037                    panic!("A cluster used in the graph was not instantiated: {}", from)
6038                })
6039                .clone();
6040            let to_node = clusters
6041                .get(to)
6042                .unwrap_or_else(|| {
6043                    panic!("A cluster used in the graph was not instantiated: {}", to)
6044                })
6045                .clone();
6046
6047            let sink_port = from_node.next_port();
6048            let source_port = to_node.next_port();
6049
6050            (
6051                D::m2m_sink_source(
6052                    env,
6053                    &from_node,
6054                    &sink_port,
6055                    &to_node,
6056                    &source_port,
6057                    name,
6058                    networking_info,
6059                    external_types,
6060                ),
6061                D::m2m_connect(&from_node, &sink_port, &to_node, &source_port),
6062            )
6063        }
6064        (LocationId::Tick(_, _), _) => panic!(),
6065        (_, LocationId::Tick(_, _)) => panic!(),
6066        (LocationId::Atomic(_), _) => panic!(),
6067        (_, LocationId::Atomic(_)) => panic!(),
6068    };
6069    (sink, source, connect_fn)
6070}
6071
6072#[cfg(test)]
6073mod serde_test;
6074
6075#[cfg(test)]
6076mod test {
6077    use std::mem::size_of;
6078
6079    use stageleft::{QuotedWithContext, q};
6080
6081    use super::*;
6082
6083    #[test]
6084    #[cfg_attr(
6085        not(feature = "build"),
6086        ignore = "expects inclusion of feature-gated fields"
6087    )]
6088    fn hydro_node_size() {
6089        assert_eq!(size_of::<HydroNode>(), 264);
6090    }
6091
6092    #[test]
6093    #[cfg_attr(
6094        not(feature = "build"),
6095        ignore = "expects inclusion of feature-gated fields"
6096    )]
6097    fn hydro_root_size() {
6098        assert_eq!(size_of::<HydroRoot>(), 136);
6099    }
6100
6101    #[test]
6102    fn test_simplify_q_macro_basic() {
6103        // Test basic non-q! expression
6104        let simple_expr: syn::Expr = syn::parse_str("x + y").unwrap();
6105        let result = simplify_q_macro(simple_expr.clone());
6106        assert_eq!(result, simple_expr);
6107    }
6108
6109    #[test]
6110    fn test_simplify_q_macro_actual_stageleft_call() {
6111        // Test a simplified version of what a real stageleft call might look like
6112        let stageleft_call = q!(|x: usize| x + 1).splice_fn1_ctx(&());
6113        let result = simplify_q_macro(stageleft_call);
6114        // This should be processed by our visitor and simplified to q!(...)
6115        // since we detect the stageleft::runtime_support::fn_* pattern
6116        hydro_build_utils::assert_snapshot!(result.to_token_stream().to_string());
6117    }
6118
6119    #[test]
6120    fn test_closure_no_pipe_at_start() {
6121        // Test a closure that does not start with a pipe
6122        let stageleft_call = q!({
6123            let foo = 123;
6124            move |b: usize| b + foo
6125        })
6126        .splice_fn1_ctx(&());
6127        let result = simplify_q_macro(stageleft_call);
6128        hydro_build_utils::assert_snapshot!(result.to_token_stream().to_string());
6129    }
6130}