Skip to main content

polydat_core/library/
identity.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Identity and constant nodes.
5
6use crate::ast::SlotShape;
7use crate::ast::{NodeMeta, PolydatNode, Port, PortType, Slot, Value};
8
9/// Passthrough: output equals input. Polymorphic via PolyWire: the
10/// runtime port type is resolved by the assembler from the upstream
11/// wire's type and passed to `Identity::new(input_type)`.
12///
13/// Compiled as a slot copy on every tier: an immediate is copied in
14/// place, a `Ref2` value into the step's own scratch (`assembly.rs`,
15/// axiom S3); the native lowering is `JitOp::Identity`.
16#[crate::polydat_node(category = Diagnostic)]
17fn identity(input: Value) -> Value {
18    input
19}
20
21/// Passthrough for external port values (captures).
22///
23/// Reads a single input (from a `WireSource::Port`) and copies it
24/// unchanged to the output. The port type is declared based on the
25/// port's default value type at construction time.
26///
27/// This node is auto-inserted by the compiler for `extern` port
28/// declarations, making captured values available as Polydat outputs.
29pub struct PortPassthrough {
30    meta: NodeMeta,
31}
32
33impl PortPassthrough {
34    /// Create a port passthrough with the given output type.
35    pub fn new(name: &str, port_type: crate::ast::PortType) -> Self {
36        Self {
37            meta: NodeMeta {
38                name: format!("__port_{name}"),
39                outs: vec![Port::new("output", port_type)],
40                ins: vec![Slot::Wire(Port::new("input", port_type))],
41            },
42        }
43    }
44}
45
46impl PolydatNode for PortPassthrough {
47    fn meta(&self) -> &NodeMeta {
48        &self.meta
49    }
50
51    fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
52        outputs[0] = inputs[0].clone();
53    }
54
55    /// A passthrough copies its slots, on every color but `Ref2`, whose
56    /// pairs may not be forwarded by an identity-style step (axiom S3).
57    /// Byte-string handles copy like scalars: a handle is a name (SRD
58    /// 115 H1). A `Ref2` output returns `None` here; the builders copy
59    /// it into the step's own scratch through `assembly::ref_copy_kit`
60    /// (axiom S3).
61    fn compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
62        if self.meta.outs[0].typ.slot_color() == crate::ast::SlotColor::Ref2 {
63            return None;
64        }
65        Some(Box::new(|inputs: &[u64], outputs: &mut [u64]| {
66            outputs.copy_from_slice(inputs)
67        }))
68    }
69}
70
71/// Emit a fixed u64 value (no inputs).
72///
73/// Signature: `const_u64(value: u64) -> (u64)`
74///
75/// Source node that always produces the same u64 regardless of cycle.
76/// Use for injecting literal parameters into a DAG, such as a fixed
77/// partition key, an epoch timestamp base, or an addend for `add`.
78/// Takes no inputs, so it sits at a DAG root.
79///
80/// JIT level: P2 (compiled_u64 emits a captured constant via the
81/// `#[polydat_node]`-emitted body capture).
82///
83/// Named per the per-type scheme shared with `const_f64` /
84/// `const_bool` (see `library::fixed`).
85#[crate::polydat_node(category = Math)]
86fn const_u64(value: crate::derive_support::Const<u64>) -> u64 {
87    *value
88}
89
90/// Emit a fixed string value (no inputs).
91///
92/// Signature: `const_str(value: String) -> (Arc<str>)`
93///
94/// Source node that always produces the same string regardless of cycle.
95/// Use for injecting literal string parameters into a DAG, such as a
96/// fixed table name, a static label, or a separator for string
97/// concatenation pipelines.
98///
99/// The compiled form publishes the `(ptr, len)` pair of the interned
100/// text (axiom S7); P3 lowers the same pair as immediates.
101///
102/// The `Const<&str>` source captures the owned `String`;
103/// `#[poly_const]` derives an `Arc<str>` cache at construction time,
104/// so per-cycle eval is a refcount bump on a single heap allocation.
105/// The macro emits `ConstStr::new(value: String)`.
106fn const_str_arc(s: &str) -> std::sync::Arc<str> {
107    std::sync::Arc::from(s)
108}
109
110/// The compiled form of a string literal: the text is interned at
111/// kernel build, and the step publishes the `(ptr, len)` pair of the
112/// interned bytes, which have process lifetime (jit_boundary.md, axiom
113/// S7). It owns no scratch and copies nothing.
114fn const_str_compiled(
115    node: &ConstStr,
116    _wire_types: &[crate::ast::PortType],
117) -> crate::ast::CompiledSlotKit {
118    let (ptr, len) = crate::kernel::static_pair(crate::kernel::StaticInterner::intern(&node.value));
119    crate::ast::CompiledSlotKit {
120        scratch: Vec::new(),
121        op: Box::new(
122            move |_inputs: &[u64], outputs: &mut [u64], _scratch: &mut [crate::ast::ScratchBuf]| {
123                outputs[0] = ptr;
124                outputs[1] = len;
125            },
126        ),
127    }
128}
129
130#[crate::polydat_node(category = Diagnostic, compiled_slot = const_str_compiled)]
131fn const_str(
132    #[poly_default("")] value: crate::derive_support::Const<&str>,
133    #[poly_const(const_str_arc, from = value)] cached: &std::sync::Arc<str>,
134) -> std::sync::Arc<str> {
135    cached.clone()
136}
137
138/// Emit a fixed [`Value::Handle`] (no inputs).
139///
140/// Signature: `const_handle() -> (Handle)`
141///
142/// Created by the constant-folding pass to replace an `init`
143/// binding whose evaluation produced a `Value::Handle` (e.g.
144/// `init prebuffered = dataset_prebuffer(...)`). Without this
145/// replacement, the original side-effect-bearing node would
146/// stay in the program graph with its eval intact, and every
147/// fresh fiber's `PolydatState` would re-fire the eval at first
148/// downstream pull — producing a per-fiber stampede that, in
149/// the prebuffer case, exhausts the per-process thread limit
150/// when vectordata's HTTP workers spin up concurrently.
151///
152/// The handle's `Arc` is cloned per `eval()` call (one atomic
153/// refcount bump); the underlying resource is shared.
154///
155/// JIT level: P1 (Handle output; no compiled_u64 path).
156pub struct ConstHandle {
157    meta: NodeMeta,
158    value: std::sync::Arc<dyn std::any::Any + Send + Sync>,
159}
160
161impl ConstHandle {
162    /// A constant node holding `value` as a handle.
163    pub fn new(value: std::sync::Arc<dyn std::any::Any + Send + Sync>) -> Self {
164        Self {
165            meta: NodeMeta {
166                name: "const_handle".into(),
167                outs: vec![Port::new("output", PortType::Handle)],
168                // No const slot — the handle is type-erased and
169                // doesn't fit the const-slot vocabulary; fold-pass
170                // synthesises this node directly with no input wires.
171                ins: vec![],
172            },
173            value,
174        }
175    }
176}
177
178impl PolydatNode for ConstHandle {
179    fn meta(&self) -> &NodeMeta {
180        &self.meta
181    }
182
183    fn eval(&self, _inputs: &[Value], outputs: &mut [Value]) {
184        outputs[0] = Value::Handle(self.value.clone());
185    }
186}
187
188/// SRD 71 — leaf const for [`Value::Ext`]-typed values
189/// (Partition, PartitionSpec, PartitionList, …).
190///
191/// Mirrors [`ConstHandle`]'s shape for `Handle`-typed values:
192/// fold-pass synthesises one of these in place of any
193/// node-with-wiring whose evaluated output is an `Ext` value,
194/// so the post-fold kernel can read the constant via
195/// `get_constant` (no input slots, eval just emits the stored
196/// value).
197pub struct ConstExt {
198    meta: NodeMeta,
199    value: Box<dyn crate::ast::ReflectedValue>,
200}
201
202impl ConstExt {
203    /// A constant node holding an extension value.
204    pub fn new(value: Box<dyn crate::ast::ReflectedValue>) -> Self {
205        Self {
206            meta: NodeMeta {
207                name: "const_ext".into(),
208                outs: vec![Port::new("output", PortType::Ext)],
209                ins: vec![],
210            },
211            value,
212        }
213    }
214}
215
216impl PolydatNode for ConstExt {
217    fn meta(&self) -> &NodeMeta {
218        &self.meta
219    }
220
221    fn eval(&self, _inputs: &[Value], outputs: &mut [Value]) {
222        outputs[0] = Value::Ext(self.value.clone());
223    }
224}