Skip to main content

polydat_core/kernel/
api.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! The kernel API: one surface for every engine.
5//!
6//! A host holds a kernel as `Box<dyn Kernel>` whatever engine built
7//! it, and drives it through [`Kernel`]: the coordinate and extern
8//! writes that invalidate their dependents, `pull` for one output and `eval` for
9//! every one, the names and types of its inputs and outputs, the
10//! traversals its program declares, its cells, and `into_program`, the
11//! program shared across threads that [`KernelProgram::create_kernel`]
12//! makes a kernel of per thread. Every engine that accepts a program
13//! computes what the interpreter computes, and every call means the
14//! same thing on every engine: the one write rule at `set_input`, one
15//! `invalidate_all`, outputs in declaration order, and a created kernel
16//! starting from the program's defaults.
17//!
18//! [`PolydatKernel`] is the interpreter's kernel as a concrete type:
19//! a program (immutable, shared through `Arc<PolydatProgram>`) and
20//! one evaluation state. It implements [`Kernel`] and keeps three
21//! traits of its own, for the interpreter alone:
22//!
23//! - [`Dataflow`], the raw read of an input wire by index or name.
24//! - [`Metadata`], structural queries the program answers directly.
25//! - [`Construction`], the subcontext protocol: a root from source
26//!   matter, a subscope built against this kernel with new matter.
27//!
28//! The construction-time hooks the compile path and the program
29//! sharing use (attaching traversals, resolving cursor extents,
30//! nesting) live on a sealed supertrait a host neither sees nor
31//! implements.
32//!
33//! [`PolydatKernel`]: super::PolydatKernel
34
35use crate::ast::{PortType, Value};
36use crate::kernel::{SharedCell, SharedCellEntry};
37
38/// Error returned by [`Kernel::set_input`] and [`Kernel::set_input_at`]
39/// when the typed-write contract at the composition-substrate boundary
40/// cannot be satisfied.
41///
42/// Per composition_substrate.md axiom S4, "T1 + T2 ensure
43/// writes are type-checked at the boundary" — the typed-write
44/// API rejects writes whose Value variant doesn't match the
45/// declared slot port type. This error names the rejection reason.
46#[derive(Debug, Clone, PartialEq)]
47pub enum WriteError {
48    /// The wire key did not resolve to a known input slot.
49    /// Carries the name that was looked up; for indexed writes
50    /// the index is reported instead.
51    UnknownWire {
52        /// The name or index looked up.
53        key: String,
54        /// The kernel's input slots, as
55        /// [`Kernel::input_names`] reports them and in the same order,
56        /// coordinates included. Empty only where the writer does not
57        /// have the list.
58        ///
59        /// Coordinates are in it although writing one by name is
60        /// [`Self::CoordinateSlot`] rather than a success: a caller who
61        /// mistyped a coordinate meant a name this kernel has, and is
62        /// not helped by a list that leaves it out. The exact match is
63        /// what the other variant is for. Every engine answers alike,
64        /// and a host can check the list against `input_names`.
65        known: Vec<String>,
66    },
67
68    /// The value's port type did not match the slot's declared
69    /// port type and no auto-adapter exists to heal the
70    /// mismatch. Both expected and provided port types are
71    /// reported for diagnostic clarity.
72    TypeMismatch {
73        /// The slot written.
74        slot: String,
75        /// Its declared type.
76        expected: PortType,
77        /// The value's type.
78        got: PortType,
79    },
80
81    /// The slot is a coordinate, which advances through
82    /// `set_inputs` rather than being written by name or index.
83    /// Writing one here would put the coordinate prefix out of
84    /// step with the values a pull is about to read.
85    CoordinateSlot {
86        /// The coordinate named.
87        slot: String,
88    },
89
90    /// The slot holds a `const` binding's value, which only
91    /// [`Kernel::init`] writes. A const is fixed for the life of the
92    /// kernel; to change it, write the inputs it reads and initialize.
93    ConstSlot {
94        /// The const's slot.
95        slot: String,
96    },
97
98    /// A value a binder copied from the parent scope does not satisfy
99    /// the type the child declares for the input of the same name. The
100    /// parent's output and the child's input share the name.
101    FromParent {
102        /// The child's input, and the parent's output it was copied from.
103        slot: String,
104        /// The type the child declares.
105        expected: PortType,
106        /// The type of the parent's value.
107        got: PortType,
108    },
109}
110
111impl std::fmt::Display for WriteError {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match self {
114            WriteError::UnknownWire { key, known } => {
115                write!(f, "unknown wire '{key}': no input slot by this name")?;
116                if !known.is_empty() {
117                    write!(f, "; this kernel's are {known:?}")?;
118                }
119                Ok(())
120            }
121            WriteError::CoordinateSlot { slot } => {
122                write!(
123                    f,
124                    "'{slot}' is a coordinate: advance it with set_inputs, not by name"
125                )
126            }
127            WriteError::ConstSlot { slot } => {
128                write!(
129                    f,
130                    "'{slot}' holds a const, which only initialization writes: write the \
131                     inputs it reads and call init()"
132                )
133            }
134            WriteError::FromParent {
135                slot,
136                expected,
137                got,
138            } => {
139                write!(
140                    f,
141                    "the parent's '{slot}' is {got:?}, but the child declares its input \
142                     '{slot}' as {expected:?}: declare the child's input with the parent's \
143                     type, or convert the value in the parent"
144                )
145            }
146            WriteError::TypeMismatch {
147                slot,
148                expected,
149                got,
150            } => {
151                write!(
152                    f,
153                    "type mismatch writing to slot '{slot}': expected {expected:?}, got {got:?} (no auto-adapter available)"
154                )?;
155                // Vec → scalar is intentionally excluded from the
156                // polyfill matrix (type_system.md §3) — there is
157                // no single natural collection-to-scalar
158                // convention. Point the author at the explicit
159                // helpers rather than leaving them to guess.
160                if matches!(got, PortType::VecF32 | PortType::VecI32)
161                    && !matches!(
162                        expected,
163                        PortType::VecF32
164                            | PortType::VecI32
165                            | PortType::Str
166                            | PortType::Bytes
167                            | PortType::Json
168                    )
169                {
170                    write!(
171                        f,
172                        " — collection → scalar requires an explicit \
173                         reduction node in the program (the library \
174                         provides none; `vec_dot` and `vec_norm` are \
175                         the vector reductions that exist)"
176                    )?;
177                }
178                Ok(())
179            }
180        }
181    }
182}
183
184impl std::error::Error for WriteError {}
185
186/// A wire reference — either a pre-resolved index (fast path)
187/// or a name (resolved against the context's input map).
188///
189/// Lets `get_wire` accept either form so callers can hold an index
190/// when they have one and a name when they don't, without needing two
191/// distinct method names.
192///
193/// Sealed: only the in-crate impls (`usize`, `&str`, `String`)
194/// are valid wire keys. External implementors are not
195/// permitted because the resolution semantics are tied to the
196/// context's input layout.
197pub trait WireKey: sealed::Sealed {
198    /// Resolve to a wire index in `metadata`. Returns `None`
199    /// when the key doesn't match a wire on this context.
200    fn resolve<M: Metadata + ?Sized>(self, metadata: &M) -> Option<usize>;
201}
202
203mod sealed {
204    pub trait Sealed {}
205    impl Sealed for usize {}
206    impl Sealed for &str {}
207    impl Sealed for String {}
208    impl Sealed for &String {}
209}
210
211impl WireKey for usize {
212    #[inline]
213    fn resolve<M: Metadata + ?Sized>(self, _: &M) -> Option<usize> {
214        Some(self)
215    }
216}
217
218impl WireKey for &str {
219    #[inline]
220    fn resolve<M: Metadata + ?Sized>(self, metadata: &M) -> Option<usize> {
221        metadata.find_input(self)
222    }
223}
224
225impl WireKey for String {
226    #[inline]
227    fn resolve<M: Metadata + ?Sized>(self, metadata: &M) -> Option<usize> {
228        metadata.find_input(&self)
229    }
230}
231
232impl WireKey for &String {
233    #[inline]
234    fn resolve<M: Metadata + ?Sized>(self, metadata: &M) -> Option<usize> {
235        metadata.find_input(self)
236    }
237}
238
239/// Read-only metadata about the interpreter's kernel: structural
240/// shape, types, names, scope layering. Everything that's a property
241/// of the compiled program (or fiber-state instance) but isn't itself
242/// a runtime value. Interpreter-only: [`Kernel`] carries the names and
243/// types every engine reports.
244pub trait Metadata {
245    /// Resolve an input name to its wire index, if present.
246    fn find_input(&self, name: &str) -> Option<usize>;
247
248    /// All declared input wire names, in declaration order.
249    fn input_names(&self) -> Vec<String>;
250
251    /// All declared output wire names, in declaration order.
252    fn output_names(&self) -> Vec<String>;
253
254    /// Number of coordinate inputs (the leading prefix of the
255    /// input slot vector — written via the cycle dispatcher).
256    fn coord_count(&self) -> usize;
257
258    /// Declared port type of an input wire, if known.
259    fn input_port_type(&self, name: &str) -> Option<PortType>;
260
261    /// Declared port type of an input wire by index. The
262    /// indexed counterpart of [`input_port_type`](Self::input_port_type),
263    /// which looks up the slot's type without first reverse-resolving
264    /// an index to a name.
265    fn input_port_type_by_idx(&self, idx: usize) -> Option<PortType>;
266
267    /// Declared port type of an output wire, if present.
268    /// Symmetric counterpart to [`input_port_type`](Self::input_port_type). Used by
269    /// the binder verification path
270    /// (`crate::binder::verify_against_kernel`) to look up wire
271    /// types for type-checking adapter binding shapes.
272    fn output_port_type(&self, name: &str) -> Option<PortType>;
273}
274
275/// The interpreter kernel's raw read of its input wires, by index or
276/// by name. Writes go through [`Kernel::set_input`] and
277/// [`Kernel::set_input_at`], which refuse a value of another type.
278/// Interpreter-only.
279pub trait Dataflow: Metadata {
280    /// Read the current value of wire `idx`. Out-of-range
281    /// behaviour returns the slot's default `Value::None` (the
282    /// read path is non-fallible; type information is structural
283    /// and reads cannot fail typewise).
284    fn get_wire_idx(&self, idx: usize) -> Value;
285
286    /// Read the current value of a wire identified by `key`
287    /// (index or name). Returns `None` when the wire is not
288    /// found.
289    #[inline]
290    fn get_wire<W: WireKey>(&self, key: W) -> Option<Value> {
291        key.resolve(self).map(|idx| self.get_wire_idx(idx))
292    }
293}
294
295/// Construction interface — the two sanctioned construction
296/// paths. Per the kernel-construction invariant:
297///
298/// 1. **Root** — built from Polydat matter, no parent.
299/// 2. **Subscope** — built from Polydat matter against an existing
300///    context.
301///
302/// Both paths take the same typed Polydat matter
303/// ([`super::subcontext::PolydatMatter`]). The only
304/// difference is whether a parent context supervises
305/// construction. Nothing else is allowed.
306pub trait Construction: Sized {
307    /// Construction error type.
308    type Error;
309
310    /// Path 1: build a root context from Polydat matter. No parent.
311    /// Subscope-only fields on the matter (result-binding
312    /// rewrites, inherited-output cascade, finalize-time
313    /// contract checks) are not applicable here and are
314    /// ignored.
315    fn root(matter: super::subcontext::PolydatMatter<'_>) -> Result<Self, Self::Error>;
316
317    /// Path 2: build a subscope context against `self` from
318    /// Polydat matter. The parent supervises: cell cascade, Rule 2
319    /// rewrites, scope-coordinate threading, init-binding
320    /// contract checks all flow from `self` into the child, which
321    /// runs on `self`'s engine
322    /// ([`super::subcontext::PolydatMatter::build_under`]).
323    fn subscope(
324        &self,
325        matter: super::subcontext::PolydatMatter<'_>,
326    ) -> Result<Box<dyn Kernel>, Self::Error>;
327}
328
329// ── One kernel API for every engine (engines.md §3.5) ──────
330
331/// A kernel on any engine: the interpreter, the closure tier, the
332/// hybrid kernel, or pure native code. Every engine accepts every
333/// program the interpreter accepts, or refuses it at construction
334/// with a reason, and computes the same values for the same inputs;
335/// the choice of engine changes how fast a program runs and nothing
336/// else. This trait is the surface a host drives an engine through
337/// without knowing which one it has.
338///
339/// The interpreter kernel and the compiled kernels also keep their
340/// inherent methods (raw slot readers, `eval(&[u64])`, `engine_counts`)
341/// as engine-specific extras; where a name is shared, the inherent
342/// method is the one a call on the concrete type reaches, and the
343/// trait's is reached through `dyn Kernel` or `Kernel::pull(&mut k, …)`.
344pub trait Kernel: Send + Sync + internals::KernelInternals {
345    /// The engine this kernel runs on.
346    fn engine(&self) -> crate::compile::select::Engine;
347
348    /// Set the coordinate inputs for the next evaluation.
349    fn set_inputs(&mut self, coords: &[u64]);
350
351    /// Set an extern by name. One rule on every engine: the value must
352    /// satisfy the declared port type (a carrier's bit-stuffed forms
353    /// included) or be `None`, which clears the extern; a value of
354    /// another type is refused at the write, never healed. A coordinate
355    /// is set with [`Self::set_inputs`], not here. An unknown name is
356    /// an error naming the known ones.
357    fn set_input(&mut self, name: &str, value: Value) -> Result<(), WriteError>;
358
359    /// Narrow a cursor to one partition: its `Ext` slot and its six
360    /// scalar projections are set.
361    fn set_cursor(
362        &mut self,
363        name: &str,
364        partition: &crate::iteration::cursor_partition::Partition,
365    ) -> Result<(), crate::kernel::WriteError>;
366
367    /// Evaluate every output for the inputs set so far.
368    fn eval(&mut self);
369
370    /// The named output for the inputs set so far, evaluating what it
371    /// needs and no more: the output's cone, on all four engines (pure
372    /// native code, though one function, runs only the fusion units
373    /// of the output's cone; engines.md §1). A side channel in
374    /// the cone fires when the output is pulled; a failing node fails
375    /// when pulled, with the same attributed message on every engine:
376    /// the node's name, the outputs it feeds, the program's context,
377    /// and its inputs. The value is owned; a handle is never returned to
378    /// the host, and a slot that holds `None` reads as `None`.
379    fn pull(&mut self, name: &str) -> Value;
380
381    /// Every input by name, the coordinates first.
382    fn input_names(&self) -> Vec<String>;
383
384    /// Every named output.
385    fn output_names(&self) -> Vec<String>;
386
387    /// The declared port type of a named output.
388    fn output_type(&self, name: &str) -> Option<PortType>;
389
390    /// The externs by name and declared type.
391    fn externs(&self) -> Vec<(String, PortType)>;
392
393    /// The cursors the program declares, with the partitions the
394    /// compiler resolved where it could.
395    fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema];
396
397    /// What this kernel's engine decided for the program: how much of
398    /// it runs as native segments, as closure steps, and on the
399    /// interpreter. The one planning detail a kernel exposes.
400    fn plan(&self) -> crate::EnginePlan;
401
402    /// The value of a named input as the kernel holds it now, an extern
403    /// or a coordinate; `None` for a name that is not an input.
404    fn input_value(&self, name: &str) -> Option<Value>;
405
406    /// The index of a named input among [`Self::input_names`], the
407    /// coordinates first: what [`Self::set_input_at`] takes.
408    fn input_index(&self, name: &str) -> Option<usize> {
409        self.input_names().iter().position(|n| n == name)
410    }
411
412    /// [`Self::set_input`] by index, for a host that binds the same
413    /// inputs every cycle: the name is resolved once, with
414    /// [`Self::input_index`], and no lookup runs per write.
415    fn set_input_at(&mut self, index: usize, value: Value) -> Result<(), WriteError> {
416        let name =
417            self.input_names()
418                .get(index)
419                .cloned()
420                .ok_or_else(|| WriteError::UnknownWire {
421                    key: format!("wire[{index}]"),
422                    known: self.input_names(),
423                })?;
424        self.set_input(&name, value)
425    }
426
427    /// The index of a named output among [`Self::output_names`]: what
428    /// [`Self::pull_at`] takes.
429    fn output_index(&self, name: &str) -> Option<usize> {
430        self.output_names().iter().position(|n| n == name)
431    }
432
433    /// [`Self::pull`] by index, for a host that reads the same outputs
434    /// every cycle: the name is resolved once, with
435    /// [`Self::output_index`], and no lookup runs per pull.
436    fn pull_at(&mut self, index: usize) -> Value {
437        let name = self
438            .output_names()
439            .get(index)
440            .cloned()
441            .unwrap_or_else(|| panic!("no output at index {index}"));
442        self.pull(&name)
443    }
444
445    /// The `const` bindings this kernel initializes, in the order
446    /// [`Self::init`] evaluates them: a const that reads another comes
447    /// after it.
448    fn const_inits(&self) -> &[crate::kernel::ConstInit];
449
450    /// Write an input as part of initialization. It is
451    /// [`Self::set_input_at`] except that a const's slot is accepted,
452    /// which is how [`Self::init`] stores each const's value.
453    fn init_input_at(&mut self, index: usize, value: Value) -> Result<(), WriteError>;
454
455    /// Initialize the kernel: evaluate every `const` binding once, in
456    /// dependency order, and store its value for the rest of the
457    /// kernel's life.
458    ///
459    /// Every way a kernel comes into existence initializes it: a build, a
460    /// kernel created from a shared program, a child bound under a
461    /// parent (after the parent's values are bound), and a traversal
462    /// activation (after its tuple is bound). A host calls it again when
463    /// it wants the consts recomputed from the inputs as they are now,
464    /// for example after setting externs a const reads. Inputs keep
465    /// their values; only the consts change.
466    ///
467    /// A const whose expression yields `None` takes the value the binder
468    /// copied from the enclosing scope, so the outer binding stays
469    /// visible. A const whose expression fails makes initialization
470    /// fail, naming the const; a slow one makes initialization slow.
471    ///
472    /// A `shared` register with a computed starting value is seeded here
473    /// too, in the same order, while nothing has written the register: a
474    /// kernel attached to a register another scope declared, and one
475    /// initialized again, leave it as it is.
476    fn init(&mut self) -> Result<(), crate::KernelError> {
477        for i in 0..self.const_inits().len() {
478            let (source, slot, fallback, register) = {
479                let c = &self.const_inits()[i];
480                (c.source_index, c.slot_index, c.fallback_index, c.register)
481            };
482            if register && register_written(self, i) {
483                continue;
484            }
485            let own =
486                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.pull_at(source)))
487                    .map_err(|payload| crate::KernelError::ConstInit {
488                        name: self.const_inits()[i].name.clone(),
489                        reason: crate::kernel::panic_message(&payload),
490                    })?;
491            let value = match own {
492                Value::None => fallback
493                    .and_then(|f| self.input_value_at(f))
494                    .unwrap_or(Value::None),
495                v => v,
496            };
497            // Pure native code carries no `None`: a const with no value
498            // is refused there, as an unset extern is (engines.md §8),
499            // rather than read as a zero.
500            if let (Value::None, engine @ crate::Engine::PureNative(_)) = (&value, self.engine()) {
501                return Err(crate::KernelError::Refused {
502                    engine,
503                    reason: format!(
504                        "the const '{}' has no value, and pure native code cannot carry a \
505                         `None`; give it a value or run this program on `native`",
506                        self.const_inits()[i].name
507                    ),
508                });
509            }
510            self.init_input_at(slot, value)
511                .map_err(crate::KernelError::Write)?;
512        }
513        Ok(())
514    }
515
516    /// The traversals the program declares, in document order.
517    fn traversals(&self) -> &[crate::dsl::traversal::Traversal];
518
519    /// Open the traversal at `index` against this kernel's current
520    /// values (for_traversal.md §3.6): the comprehension's sources see
521    /// the wires they reference as this kernel holds them now, and the
522    /// cascaded wires are snapshotted into every activation. Every
523    /// engine opens traversals (engines.md §3.6).
524    fn traverse(&mut self, index: usize) -> Result<crate::kernel::TraversalStream, String>;
525
526    /// Open every traversal, in document order.
527    fn traverse_all(&mut self) -> Result<Vec<crate::kernel::TraversalStream>, String> {
528        (0..self.traversals().len())
529            .map(|i| self.traverse(i))
530            .collect()
531    }
532
533    /// Begin the next cycle with nothing current, so every step, a side
534    /// channel included, runs again when pulled. The runtime model makes
535    /// a cycle whose inputs did not move cost nothing; this is how a
536    /// host runs such a cycle anyway, as the `polydat` binary does when
537    /// every input is fixed.
538    fn invalidate_all(&mut self);
539
540    /// The cells this kernel's `shared` bindings are bound to (scope
541    /// model §6): one register per binding, which every kernel holding
542    /// the cell reads and writes.
543    fn shared_cells(&self) -> Vec<SharedCellEntry>;
544
545    /// The broadcast cell for a *computed* output, created on the first
546    /// ask: a descendant that binds its matching input slot to this
547    /// cell reads the value each of this kernel's pulls publishes
548    /// through it, rather than a copy taken once when the descendant
549    /// was built (cross_fiber_invalidation.md §3.1).
550    ///
551    /// `None` when the name is not an output of this kernel. All four
552    /// engines have broadcast cells: the interpreter seeds one per
553    /// output at construction, and the closure tier, native, and pure
554    /// native make them on demand, so a compiled program with no
555    /// descendant bound to it allocates none (engines.md §3.6).
556    fn output_cell(&self, _name: &str) -> Option<SharedCell> {
557        None
558    }
559
560    /// The binding modifier a named output was declared with — `const`,
561    /// `shared`, `final`, or none. A binder reads it to decide how a
562    /// descendant takes the output: a `const` is effectively fixed for
563    /// the scope's life and is value-copied, where a computed output is
564    /// bound to its broadcast cell (scope_model.md §4).
565    ///
566    /// `NONE` for a name this kernel does not declare.
567    fn output_modifier(&self, _name: &str) -> crate::dsl::ast::BindingModifier {
568        crate::dsl::ast::BindingModifier::NONE
569    }
570
571    /// Every cell a descendant of this kernel could bind to: the ones
572    /// its own `shared` slots hold, plus the ones it carries forward
573    /// for a descendant without holding a slot for them itself. The
574    /// second kind is why an ancestral `shared` reaches a grandchild
575    /// whose parent's program never names it.
576    ///
577    /// [`Self::shared_cells`] is the first kind alone.
578    fn cells_in_scope(&self) -> Vec<SharedCellEntry> {
579        self.shared_cells()
580    }
581
582    /// Carry `cells` forward for this kernel's descendants. The binder
583    /// writes what the parent had and this kernel holds no slot for.
584    fn set_transit_cells(&mut self, _cells: Vec<SharedCellEntry>) {}
585
586    /// This kernel's place in the comprehension nest its scope was
587    /// built under, outermost last: a child's path is its own followed
588    /// by its parent's. Empty for a root, which is every kernel a host
589    /// compiles rather than binds, so the compiled engines answer
590    /// empty until one is bound under a parent.
591    fn scope_coordinates(&self) -> &[super::ScopeCoord] {
592        &[]
593    }
594
595    /// Append `outer` to this kernel's own scope-coordinate path, which
596    /// the binder does once the child's inputs are in. A no-op on an
597    /// engine that keeps no path.
598    fn extend_scope_coordinates(&mut self, _outer: &[super::ScopeCoord]) {}
599
600    /// The declared type of a named input slot, coordinates included.
601    /// The binder reads it to adapt a value the parent supplies into
602    /// the type the child's slot declares.
603    fn input_port_type(&self, _name: &str) -> Option<PortType> {
604        None
605    }
606
607    /// Bind the named input slot to `cell`, whether or not the slot was
608    /// built as a `shared` register, and answer whether it was bound.
609    ///
610    /// This is what a parent does to a child, not what a host does to
611    /// two kernels. A child declares its imports `extern`; it is the
612    /// parent binding it that decides one of them reads a register
613    /// rather than a copied value. [`Self::attach_shared_cell`] is the
614    /// host's operation and refuses a slot that is not already a
615    /// register on both sides, which is the right answer for joining
616    /// two kernels and the wrong one for building a child.
617    fn bind_input_cell(&mut self, _name: &str, _cell: SharedCell) -> bool {
618        false
619    }
620
621    /// Bind the `shared` binding `name` to `cell`, so this kernel and
622    /// every other holder of the cell read and write one register:
623    /// a write on any of them is what the others read next, and a
624    /// dependent output is recomputed. A name that is not a `shared`
625    /// binding is an error naming the ones that are.
626    fn attach_shared_cell(&mut self, name: &str, cell: SharedCell) -> Result<(), String>;
627
628    /// The program this kernel runs, shareable across threads: each
629    /// thread creates its own kernel from it with
630    /// [`KernelProgram::create_kernel`].
631    ///
632    /// **What this kernel was set to does not travel with it.** A
633    /// kernel created from the program starts at the program: every
634    /// extern at its declared default and every `shared` binding with
635    /// a cell of its own, whatever this kernel had been written to
636    /// before it became one. That holds on every engine.
637    ///
638    /// The reason is that an extern is per-kernel state, in the same
639    /// family as the coordinates: both are writes into declared slots
640    /// of a running kernel, and neither is part of the compiled
641    /// program. A host that wants a value fixed *for the program*
642    /// fixes it before compiling, with
643    /// [`transform::assign_values`](crate::dsl::transform::assign_values)
644    /// or an `extern` default in the source; a host that wants every
645    /// thread to see one register attaches a cell with
646    /// [`Self::attach_shared_cell`].
647    fn into_program(self: Box<Self>) -> std::sync::Arc<dyn KernelProgram>;
648
649    /// The compile ledger of the program tree this kernel belongs to:
650    /// what compiling it and everything opened from it has built.
651    fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger>;
652
653    /// The resource scope of the program tree this kernel belongs to:
654    /// the slot for the host's [`ResourceAccessor`](crate::ResourceAccessor)
655    /// that every node of the tree looks resources up through. A host
656    /// that did not hand one to the compile
657    /// (`CompileOptions::resources`) installs its accessor here, once;
658    /// kernels created from or forked off this one, subscopes built
659    /// under it, and its traversal bodies share the scope.
660    fn resources(&self) -> &crate::resource::ResourceScope;
661
662    /// The canonical hash of this kernel's program (scope_model.md §8):
663    /// equal for one program built on any of the four engines, and for
664    /// every kernel created from or forked off it, and a function of
665    /// what the program computes rather than of its source text. What a
666    /// host keys a checkpoint on.
667    fn canonical_hash(&self) -> [u8; 32];
668
669    /// The instance hash of this kernel's program under `ancestors`,
670    /// innermost first (scope_model.md §8.2): what
671    /// [`PolydatProgram::instance_hash`](crate::kernel::PolydatProgram::instance_hash)
672    /// gives for the same programs, whichever engines the kernels are on.
673    fn instance_hash(&self, ancestors: &[&dyn Kernel]) -> [u8; 32] {
674        let chain: Vec<[u8; 32]> = ancestors.iter().map(|a| a.canonical_hash()).collect();
675        crate::kernel::instance_hash_of(self.canonical_hash(), &chain)
676    }
677
678    /// Whether `other` runs the same program (scope_model.md §8.3): their
679    /// canonical hashes are equal, whichever engines the two are on.
680    fn is_equivalent_to(&self, other: &dyn Kernel) -> bool {
681        self.canonical_hash() == other.canonical_hash()
682    }
683
684    /// Whether this kernel's program adds nothing `parent`'s does not
685    /// already supply (scope_model.md §8.3): it is equivalent to
686    /// `parent`, or it outputs nothing but its own inputs and every
687    /// input it declares `parent` declares too. The answer
688    /// [`PolydatProgram::is_subset_of`](crate::kernel::PolydatProgram::is_subset_of)
689    /// gives for the same programs, on any engines.
690    fn is_subset_of(&self, parent: &dyn Kernel) -> bool {
691        if self.is_equivalent_to(parent) {
692            return true;
693        }
694        let inputs = Kernel::input_names(self);
695        if Kernel::output_names(self)
696            .iter()
697            .any(|name| !inputs.contains(name))
698        {
699            return false;
700        }
701        let parent_inputs = Kernel::input_names(parent);
702        inputs.iter().all(|name| parent_inputs.contains(name))
703    }
704
705    // ── The per-cycle scope-tree surface (native_scope_trees.md §3) ──
706    //
707    // Index arguments are positions in `input_names`, coordinates
708    // first, resolved once by the host. None of these allocates or
709    // looks a name up.
710
711    /// How many of the inputs are coordinates: they come first in
712    /// `input_names`, and `set_inputs` writes them.
713    fn coord_count(&self) -> usize;
714
715    /// The value input `index` holds now: a coordinate's pending or
716    /// current value, an extern's current value. `None` past the end.
717    fn input_value_at(&self, index: usize) -> Option<Value>;
718
719    /// The value input `index` starts with: an extern's declared
720    /// default, `U64(0)` for a coordinate. `None` past the end.
721    fn input_default_at(&self, index: usize) -> Option<Value>;
722
723    /// Whether input `index` is bound to a shared cell, so that its
724    /// value is the cell's and a reset leaves it alone.
725    fn input_is_cell_bound(&self, index: usize) -> bool;
726
727    /// Every input that is not a coordinate and not bound to a cell
728    /// back at its default, and whatever depends on a changed one not
729    /// current. What a host does at a boundary where values written for
730    /// the last stretch must not leak into the next.
731    fn reset_inputs(&mut self);
732
733    /// A new kernel over the same program with this kernel's state: its
734    /// inputs, its current outputs, and its cells, which stay shared
735    /// (a cell is the scope's register, not a value it holds), transit
736    /// cells included. Callable concurrently on a kernel shared across
737    /// threads (native_scope_trees.md §4).
738    fn fork(&self) -> Box<dyn Kernel>;
739
740    /// Pull every output a descendant bound to by cell, so the
741    /// descendant reads the current value. Nothing happens on a kernel
742    /// nothing is bound under. A failing output is left for the pull
743    /// that needs it to report.
744    fn publish_broadcasts(&mut self);
745
746    /// Commit the Rule 2 write-throughs: pull each synthetic
747    /// `__write_<name>` output and write it through the cell of the
748    /// shared binding it exports to. No-op for a kernel without them.
749    fn commit_write_throughs(&mut self) -> Result<(), String>;
750
751    /// How input `name`'s type was established: written by the author,
752    /// or inferred by the compiler and so open to
753    /// `CompileOptions::input_variance` (input_variance.md §3). A host
754    /// that compiles many scopes at `Info` reports each open input once
755    /// from here rather than from every compile's log.
756    fn input_type_origin(&self, name: &str) -> Option<crate::kernel::TypeOrigin>;
757
758    /// The identity of this kernel's program: equal for kernels created
759    /// from one program and for forks, different for any two programs,
760    /// the same program compiled twice included. What a host seals a
761    /// plan of pre-resolved indices against.
762    fn program_id(&self) -> ProgramId;
763
764    /// The interpreter's kernel, when this is one: what a caller that
765    /// needs the interpreter's own extras ([`Metadata`], [`Dataflow`],
766    /// its program and state) reaches them through. `None` on a
767    /// compiled engine.
768    fn as_interpreter(&self) -> Option<&crate::kernel::PolydatKernel> {
769        None
770    }
771
772    /// [`Self::as_interpreter`], mutably.
773    fn as_interpreter_mut(&mut self) -> Option<&mut crate::kernel::PolydatKernel> {
774        None
775    }
776}
777
778/// Whether the `shared` register the `index`th init seeds has been
779/// written: its cell has a revision, from the scope that declared it or
780/// from any writer since. A register held in no cell is never counted as
781/// written.
782fn register_written<K: Kernel + ?Sized>(kernel: &K, index: usize) -> bool {
783    let name = &kernel.const_inits()[index].slot;
784    kernel.shared_cells().iter().any(|entry| {
785        &entry.name == name
786            && entry
787                .cell
788                .revision
789                .load(std::sync::atomic::Ordering::Acquire)
790                > 0
791    })
792}
793
794/// The identity of a compiled program; see [`Kernel::program_id`].
795#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
796pub struct ProgramId(pub(crate) usize);
797
798/// The construction-time hooks of a kernel, sealed: the compile path
799/// and the program sharing call them once, before a kernel is shared,
800/// and a host neither sees nor implements them.
801pub(crate) mod internals {
802    use crate::ast::{PortType, Value};
803
804    pub trait KernelInternals {
805        /// Attach the traversals the program declares and the producer
806        /// bindings they may traverse.
807        fn set_traversals(
808            &mut self,
809            traversals: Vec<crate::dsl::traversal::Traversal>,
810            producers: Vec<crate::dsl::traversal::Producer>,
811        );
812
813        /// The value at a buffer slot decoded as `ty`, for the compile
814        /// log's record of the constants folded at build; `None` on the
815        /// interpreter, whose program logs its own fold.
816        fn slot_value(&self, _slot: usize, _ty: PortType) -> Value {
817            Value::None
818        }
819
820        /// The value the build folded for output `name`, if it folded
821        /// one: what the compile path reads to resolve a cursor extent
822        /// computed from constants, on every engine.
823        fn folded_value(&self, name: &str) -> Option<Value>;
824
825        /// Record the extent of cursor `index` once the compile path
826        /// has resolved it from the folded constants.
827        fn set_cursor_extent(&mut self, index: usize, extent: u64);
828
829        /// Start over from the program: every input at its declared
830        /// default, every `shared` binding with a cell of its own,
831        /// nothing current. What a kernel created from a shared program
832        /// starts with; the interpreter's is built that way and needs
833        /// nothing.
834        fn reset_to_program(&mut self) {}
835
836        /// Mark `names` as the outputs this kernel's program re-exports
837        /// for its descendants without owning them, before the program
838        /// is shared: part of the program's canonical hash on every
839        /// engine (scope_model.md §8).
840        fn set_inherited_outputs(&mut self, names: Vec<String>);
841
842        /// Record the Rule 2 write-throughs this kernel commits, as
843        /// `(export_name, source_output)` pairs: what a scope module
844        /// hands the kernels it instantiates.
845        fn set_write_throughs(&mut self, pairs: Vec<(String, String)>);
846    }
847}
848
849/// A program on some engine, shared across threads through an `Arc`;
850/// every kernel created from it computes the same values and owns its
851/// own inputs, buffers, and outputs.
852pub trait KernelProgram: Send + Sync {
853    /// The engine the program was built for.
854    fn engine(&self) -> crate::compile::select::Engine;
855
856    /// A kernel of this program for the calling thread, initialized. It
857    /// starts from the program on every engine: every input at its
858    /// declared default, whatever the kernel that became the program had
859    /// been set to; every `shared` binding with a cell of its own; and
860    /// every `const` evaluated from those inputs ([`Kernel::init`]).
861    ///
862    /// # Panics
863    ///
864    /// When a const's expression fails. A const is evaluated when a
865    /// kernel is initialized, so a const that fails makes creation fail;
866    /// the build of the program evaluated the same consts from the same
867    /// defaults, so this happens only for a const that reads something
868    /// outside the program, such as a clock or the environment.
869    fn create_kernel(self: std::sync::Arc<Self>) -> Box<dyn Kernel> {
870        let mut kernel = self.create_uninitialized();
871        if let Err(e) = kernel.init() {
872            panic!("{e}");
873        }
874        kernel
875    }
876
877    /// A kernel of this program whose consts are not yet evaluated: what
878    /// a binder creates, writes the enclosing scope's values into, and
879    /// then initializes, so the consts are evaluated once, from the
880    /// bound values.
881    fn create_uninitialized(self: std::sync::Arc<Self>) -> Box<dyn Kernel>;
882
883    /// The interpreter's program, when this is one: the graph a
884    /// diagnostic describes node by node. `None` for a compiled
885    /// engine's program.
886    fn as_interpreter(
887        self: std::sync::Arc<Self>,
888    ) -> Option<std::sync::Arc<crate::kernel::PolydatProgram>> {
889        None
890    }
891
892    /// The compile ledger of the program tree this program belongs to.
893    fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger>;
894
895    /// The resource scope of the program tree this program belongs to,
896    /// the one [`Kernel::resources`] reports for every kernel created
897    /// from it.
898    fn resources(&self) -> &crate::resource::ResourceScope;
899
900    /// The canonical hash of this program, the one
901    /// [`Kernel::canonical_hash`] reports for every kernel created from
902    /// it.
903    fn canonical_hash(&self) -> [u8; 32];
904
905    /// This program's identity, equal to [`Kernel::program_id`] of every
906    /// kernel created from it.
907    fn program_id(&self) -> ProgramId;
908}
909
910/// A compiled kernel as a shared program: its steps are shared, and a
911/// created kernel is a clone that owns its own buffer, table, scratch,
912/// and externs.
913pub(crate) struct SharedKernel<K>(pub(crate) K);
914
915impl<K: Kernel + Clone + Send + Sync + 'static> KernelProgram for SharedKernel<K> {
916    fn engine(&self) -> crate::compile::select::Engine {
917        self.0.engine()
918    }
919    fn create_uninitialized(self: std::sync::Arc<Self>) -> Box<dyn Kernel> {
920        let mut kernel = self.0.clone();
921        // A created kernel starts from the program, as an interpreter
922        // state created from one does: inputs at their defaults, cells
923        // of its own; a host sets what it wants and attaches what it
924        // shares.
925        kernel.reset_to_program();
926        Box::new(kernel)
927    }
928    fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
929        self.0.ledger()
930    }
931    fn resources(&self) -> &crate::resource::ResourceScope {
932        self.0.resources()
933    }
934    fn canonical_hash(&self) -> [u8; 32] {
935        self.0.canonical_hash()
936    }
937    fn program_id(&self) -> ProgramId {
938        self.0.program_id()
939    }
940}