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