Skip to main content

polydat/kernel/
api_impl.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Trait implementations of the GK context API ([`Metadata`],
5//! [`Dataflow`], [`Construction`]) on [`GkKernel`].
6//!
7//! GkKernel is the singular caller-facing interface that fuses
8//! the compiled context (program) and per-fiber state. All
9//! external (non-GK-internal) callers should reach the kernel
10//! exclusively through these three traits — `state()` /
11//! `state_ref()` / `program()` are kernel-internal hooks.
12
13use crate::kernel::{Dataflow, GkKernel, Metadata, Construction};
14use crate::node::{PortType, Value};
15
16impl Metadata for GkKernel {
17    #[inline]
18    fn find_input(&self, name: &str) -> Option<usize> {
19        self.program().find_input(name)
20    }
21
22    #[inline]
23    fn input_names(&self) -> Vec<String> {
24        self.program().input_names()
25    }
26
27    #[inline]
28    fn output_names(&self) -> Vec<String> {
29        self.program().output_names().iter().map(|s| s.to_string()).collect()
30    }
31
32    #[inline]
33    fn coord_count(&self) -> usize {
34        self.program().coord_count()
35    }
36
37    #[inline]
38    fn input_port_type(&self, name: &str) -> Option<PortType> {
39        self.program().input_port_type(name)
40    }
41}
42
43impl Dataflow for GkKernel {
44    #[inline]
45    fn set_wire_idx(&mut self, idx: usize, value: Value) {
46        self.state().set_input(idx, value);
47    }
48
49    #[inline]
50    fn get_wire_idx(&self, idx: usize) -> Value {
51        self.state_ref().get_input(idx)
52    }
53}
54
55impl Construction for GkKernel {
56    type Error = crate::subcontext::ContractViolation;
57
58    fn root(matter: crate::subcontext::GkMatter<'_>) -> Result<Self, Self::Error> {
59        use crate::subcontext::GkMatterInner;
60        match matter.inner {
61            GkMatterInner::Source(s) => {
62                crate::dsl::compile::compile_gk_with_libs_and_limit(
63                    &s.body,
64                    s.options.workload_dir.as_deref(),
65                    s.options.gk_lib_paths,
66                    &s.options.required_outputs,
67                    s.options.strict,
68                    s.options.context_label.as_deref().unwrap_or(&s.label),
69                    s.options.cursor_limit,
70                )
71                .map_err(crate::subcontext::ContractViolation::Compile)
72            }
73            GkMatterInner::Statements(s) => {
74                // Pre-parsed AST — go through the compile-from-AST
75                // path. The `GkFile` AST root takes the statements
76                // verbatim; the same options surface as the source
77                // path.
78                let file = crate::dsl::ast::GkFile { statements: s.statements };
79                crate::dsl::compile::compile_ast_with_libs(
80                    &file,
81                    s.options.workload_dir.as_deref(),
82                    s.options.gk_lib_paths,
83                    &s.options.required_outputs,
84                    s.options.strict,
85                    s.options.context_label.as_deref().unwrap_or(&s.label),
86                )
87                .map_err(crate::subcontext::ContractViolation::Compile)
88            }
89            GkMatterInner::Program(p) => {
90                let mut k = GkKernel::from_program(p.program);
91                for (var, value) in p.iter_bindings {
92                    if let Some(idx) = k.program().find_input(var) {
93                        k.state().set_input(idx, value.clone());
94                    }
95                }
96                Ok(k)
97            }
98        }
99    }
100
101    fn subscope(
102        &self,
103        matter: crate::subcontext::GkMatter<'_>,
104    ) -> Result<Self, Self::Error> {
105        // Delegate to GkKernel's existing typed subscope path.
106        GkKernel::build_subscope(self, matter)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::dsl::compile::compile_gk;
114
115    /// Indexed wire access works.
116    #[test]
117    fn dataflow_indexed_set_get() {
118        let mut k = compile_gk(
119            "input cycle: u64\nconst x := 7\n"
120        ).unwrap();
121        // cycle is index 0
122        k.set_wire(0_usize, Value::U64(42));
123        assert_eq!(k.get_wire(0_usize), Some(Value::U64(42)));
124    }
125
126    /// Named wire access resolves through metadata.
127    #[test]
128    fn dataflow_named_set_get() {
129        let mut k = compile_gk(
130            "input cycle: u64\nextern n: u64\n"
131        ).unwrap();
132        k.set_wire("n", Value::U64(5));
133        match k.get_wire("n") {
134            Some(Value::U64(5)) => {}
135            other => panic!("expected U64(5), got {other:?}"),
136        }
137    }
138
139    /// String key works alongside &str.
140    #[test]
141    fn dataflow_string_key() {
142        let mut k = compile_gk(
143            "input cycle: u64\nextern n: u64\n"
144        ).unwrap();
145        let name = String::from("n");
146        k.set_wire(&name, Value::U64(99));
147        assert_eq!(k.get_wire(name.clone()), Some(Value::U64(99)));
148    }
149
150    /// Unknown name returns false / None — no panic.
151    #[test]
152    fn dataflow_unknown_name_safe() {
153        let mut k = compile_gk("input cycle: u64\n").unwrap();
154        assert!(!k.set_wire("nonexistent", Value::U64(1)));
155        assert!(k.get_wire("nonexistent").is_none());
156    }
157
158    /// Metadata trait surfaces names + types.
159    #[test]
160    fn metadata_listings() {
161        let k = compile_gk(
162            "input (cycle: u64, thread: u64)\nextern n: u64\nconst x := 7\n"
163        ).unwrap();
164        let inputs: Vec<String> = k.input_names();
165        assert!(inputs.iter().any(|s| s == "cycle"));
166        assert!(inputs.iter().any(|s| s == "n"));
167        assert_eq!(k.coord_count(), 2); // cycle + thread
168        assert!(k.find_input("n").is_some());
169        assert_eq!(k.input_port_type("n"), Some(PortType::U64));
170    }
171
172    /// Construction trait — both paths take the same gk
173    /// matter type. Verify symmetry: root from source, then
174    /// subscope from source against the root.
175    #[test]
176    fn construction_symmetric_paths() {
177        let root_opts = crate::subcontext::CompileOptions {
178            workload_dir: None,
179            gk_lib_paths: Vec::new(),
180            strict: false,
181            required_outputs: Vec::new(),
182            context_label: Some("root".to_string()),
183            cursor_limit: None,
184            ..Default::default()
185        };
186        let root_matter = crate::subcontext::GkMatter::builder()
187            .label("root")
188            .source("input cycle: u64\nshared flag := 0\n")
189            .options(root_opts)
190            .build()
191            .expect("matter build");
192        let root = <GkKernel as Construction>::root(root_matter)
193            .expect("root from source matter");
194
195        let sub_opts = crate::subcontext::CompileOptions {
196            workload_dir: None,
197            gk_lib_paths: Vec::new(),
198            strict: false,
199            required_outputs: Vec::new(),
200            context_label: Some("sub".to_string()),
201            cursor_limit: None,
202            ..Default::default()
203        };
204        let sub_matter = crate::subcontext::GkMatter::builder()
205            .label("sub")
206            .source("input cycle: u64\n")
207            .options(sub_opts)
208            .build()
209            .expect("matter build");
210        let _sub = root
211            .subscope(sub_matter)
212            .expect("subscope from source matter");
213    }
214
215    /// Root construction also accepts pre-compiled program
216    /// matter (re-instance with fresh state). Verifies via
217    /// the input slot — `n` is an extern input.
218    #[test]
219    fn construction_root_from_program() {
220        let template = compile_gk("input cycle: u64\nextern n: u64\n").unwrap();
221        let program = template.program().clone();
222        let matter = crate::subcontext::GkMatter::builder()
223            .program(program)
224            .build()
225            .expect("matter build");
226        let mut root = <GkKernel as Construction>::root(matter)
227            .expect("root from program matter");
228        root.set_wire("n", Value::U64(13));
229        assert_eq!(root.get_wire("n"), Some(Value::U64(13)));
230    }
231
232    /// Builder rejects ambiguous matter (multiple input forms).
233    #[test]
234    fn builder_rejects_multiple_forms() {
235        let template = compile_gk("input cycle: u64\n").unwrap();
236        match crate::subcontext::GkMatter::builder()
237            .source("input cycle: u64\n")
238            .program(template.program().clone())
239            .build()
240        {
241            Err(msg) => assert!(msg.contains("multiple"), "expected multiple-forms error, got: {msg}"),
242            Ok(_) => panic!("multiple forms must error"),
243        }
244    }
245
246    /// Builder rejects empty matter.
247    #[test]
248    fn builder_rejects_empty() {
249        match crate::subcontext::GkMatter::builder().build() {
250            Err(msg) => assert!(msg.contains("no input form"), "expected no-form error, got: {msg}"),
251            Ok(_) => panic!("empty matter must error"),
252        }
253    }
254}