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}