1use crate::ast::{PortType, Value};
14use crate::kernel::{Construction, Dataflow, Metadata, PolydatKernel};
15
16impl Metadata for PolydatKernel {
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()
30 .output_names()
31 .iter()
32 .map(|s| s.to_string())
33 .collect()
34 }
35
36 #[inline]
37 fn coord_count(&self) -> usize {
38 self.program().coord_count()
39 }
40
41 #[inline]
42 fn input_port_type(&self, name: &str) -> Option<PortType> {
43 self.program().input_port_type(name)
44 }
45
46 #[inline]
47 fn input_port_type_by_idx(&self, idx: usize) -> Option<PortType> {
48 self.program().input_port_type_by_idx(idx)
49 }
50
51 #[inline]
52 fn output_port_type(&self, name: &str) -> Option<PortType> {
53 self.program().output_port_type(name)
54 }
55}
56
57impl Dataflow for PolydatKernel {
58 fn set_wire_idx(
59 &mut self,
60 idx: usize,
61 value: Value,
62 ) -> Result<(), crate::kernel::api::WriteError> {
63 use crate::kernel::api::WriteError;
64
65 let slot_type = match self.program().input_port_type_by_idx(idx) {
71 Some(t) => t,
72 None => {
73 return Err(WriteError::UnknownWire {
74 key: format!("wire[{idx}]"),
75 });
76 }
77 };
78
79 let slot_name = self
80 .program()
81 .input_name_by_idx(idx)
82 .map(|s| s.to_string())
83 .unwrap_or_else(|| format!("wire[{idx}]"));
84
85 let got = value.port_type();
95 let adapted = crate::kernel::state::adapt_boundary_value(&slot_name, slot_type, value);
96 if !adapted.satisfies_slot(slot_type) {
107 return Err(WriteError::TypeMismatch {
108 slot: slot_name,
109 expected: slot_type,
110 got,
111 });
112 }
113 self.state().set_input(idx, adapted);
114 Ok(())
115 }
116
117 #[inline]
118 fn get_wire_idx(&self, idx: usize) -> Value {
119 self.state_ref().get_input(idx)
120 }
121}
122
123impl Construction for PolydatKernel {
124 type Error = crate::kernel::subcontext::ContractViolation;
125
126 fn root(matter: crate::kernel::subcontext::PolydatMatter<'_>) -> Result<Self, Self::Error> {
127 use crate::kernel::subcontext::PolydatMatterInner;
128 match matter.inner {
129 PolydatMatterInner::Source(s) => {
130 let options = crate::dsl::compile::CompileOptions {
131 source_dir: s.options.workload_dir.clone(),
132 lib_paths: s.options.polydat_lib_paths,
133 required_outputs: s.options.required_outputs.clone(),
134 strict: s.options.strict,
135 context: s
136 .options
137 .context_label
138 .clone()
139 .unwrap_or_else(|| s.label.clone()),
140 cursor_limit: s.options.cursor_limit,
141 };
142 crate::dsl::compile::compile_polydat_with_options(&s.body, &options, None)
143 .map_err(crate::kernel::subcontext::ContractViolation::Compile)
144 }
145 PolydatMatterInner::Statements(s) => {
146 let file = crate::dsl::ast::PolydatFile {
151 statements: s.statements,
152 };
153 let options = crate::dsl::compile::CompileOptions {
154 source_dir: s.options.workload_dir.clone(),
155 lib_paths: s.options.polydat_lib_paths,
156 required_outputs: s.options.required_outputs.clone(),
157 strict: s.options.strict,
158 context: s
159 .options
160 .context_label
161 .clone()
162 .unwrap_or_else(|| s.label.clone()),
163 cursor_limit: None,
164 };
165 crate::dsl::compile::compile_ast_with_options(&file, "", &options, None)
166 .map_err(crate::kernel::subcontext::ContractViolation::Compile)
167 }
168 PolydatMatterInner::Program(p) => {
169 let mut k = PolydatKernel::from_program(p.program);
170 for (var, value) in p.iter_bindings {
171 if let Some(idx) = k.program().find_input(var) {
172 k.state().set_input(idx, value.clone());
173 }
174 }
175 Ok(k)
176 }
177 }
178 }
179
180 fn subscope(
181 &self,
182 matter: crate::kernel::subcontext::PolydatMatter<'_>,
183 ) -> Result<Self, Self::Error> {
184 PolydatKernel::build_subscope(self, matter)
186 }
187}
188
189impl crate::kernel::Kernel for PolydatKernel {
192 fn engine(&self) -> crate::compile::select::Engine {
193 crate::compile::select::Engine::Interpreter(self.program().cone_mode())
194 }
195 fn set_inputs(&mut self, coords: &[u64]) {
196 PolydatKernel::set_inputs(self, coords);
197 }
198 fn set_input(&mut self, name: &str, value: Value) -> Result<(), String> {
199 PolydatKernel::set_input(self, name, value)
200 }
201 fn set_cursor(
202 &mut self,
203 name: &str,
204 partition: &crate::iteration::cursor_partition::Partition,
205 ) -> Result<(), String> {
206 PolydatKernel::set_cursor(self, name, partition)
207 }
208 fn eval(&mut self) {
211 for name in Metadata::output_names(self) {
212 let _ = PolydatKernel::pull(self, &name);
213 }
214 }
215 fn pull(&mut self, name: &str) -> Value {
216 PolydatKernel::pull(self, name).clone()
217 }
218 fn input_names(&self) -> Vec<String> {
219 Metadata::input_names(self)
220 }
221 fn output_names(&self) -> Vec<String> {
222 Metadata::output_names(self)
223 }
224 fn output_type(&self, name: &str) -> Option<PortType> {
225 Metadata::output_port_type(self, name)
226 }
227 fn externs(&self) -> Vec<(String, PortType)> {
228 let program = self.program();
229 Metadata::input_names(self)
230 .into_iter()
231 .enumerate()
232 .filter(|(i, _)| program.input_kind(*i) != Some(crate::kernel::InputKind::Coordinate))
233 .filter_map(|(i, name)| Metadata::input_port_type_by_idx(self, i).map(|t| (name, t)))
234 .collect()
235 }
236 fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
237 self.program().cursor_schemas()
238 }
239 fn input_value(&self, name: &str) -> Option<Value> {
240 let idx = self.program().find_input(name)?;
241 Some(self.state_ref().get_input(idx))
242 }
243 fn input_index(&self, name: &str) -> Option<usize> {
244 self.program().find_input(name)
245 }
246 fn set_input_at(&mut self, index: usize, value: Value) -> Result<(), String> {
247 PolydatKernel::set_input_at(self, index, value)
248 }
249 fn output_index(&self, name: &str) -> Option<usize> {
250 self.program().output_index(name)
251 }
252 fn pull_at(&mut self, index: usize) -> Value {
253 PolydatKernel::pull_by_index(self, index).clone()
254 }
255 fn traversals(&self) -> &[crate::dsl::traversal::Traversal] {
256 self.program().traversals()
257 }
258 fn plan(&self) -> crate::EnginePlan {
259 self.program().engine_plan()
260 }
261 fn traverse(&mut self, index: usize) -> Result<crate::kernel::TraversalStream, String> {
262 PolydatKernel::traverse(self, index)
263 }
264 fn invalidate_all(&mut self) {
265 self.state().invalidate_all();
266 }
267 fn shared_cells(&self) -> Vec<crate::kernel::SharedCellEntry> {
268 self.shared_cells_in_scope()
269 }
270 fn attach_shared_cell(
271 &mut self,
272 name: &str,
273 cell: crate::kernel::SharedCell,
274 ) -> Result<(), String> {
275 let program = self.program().clone();
276 let shared = program.shared_outputs();
277 let idx = program.find_input(name).filter(|_| shared.contains(&name));
278 let Some(idx) = idx else {
279 return Err(format!(
280 "no `shared` binding named '{name}'; this kernel's shared bindings are {shared:?}"
281 ));
282 };
283 self.state().attach_shared_cell(idx, cell);
284 Ok(())
285 }
286 fn into_program(self: Box<Self>) -> std::sync::Arc<dyn crate::kernel::KernelProgram> {
287 PolydatKernel::into_program(*self)
288 }
289}
290
291impl crate::kernel::KernelInternals for PolydatKernel {
292 fn set_traversals(
293 &mut self,
294 traversals: Vec<crate::dsl::traversal::Traversal>,
295 producers: Vec<crate::dsl::traversal::Producer>,
296 ) {
297 PolydatKernel::set_traversals(self, traversals, producers);
298 }
299 fn folded_value(&self, name: &str) -> Option<Value> {
300 self.get_constant(name).cloned()
301 }
302 fn set_cursor_extent(&mut self, index: usize, extent: u64) {
303 let mut schemas = self.program().cursor_schemas().to_vec();
304 if let Some(schema) = schemas.get_mut(index) {
305 schema.extent = Some(extent);
306 self.set_cursor_schemas(schemas);
307 }
308 }
309}
310
311impl crate::kernel::KernelProgram for crate::kernel::PolydatProgram {
312 fn engine(&self) -> crate::compile::select::Engine {
313 crate::compile::select::Engine::Interpreter(self.cone_mode())
314 }
315 fn as_interpreter(self: std::sync::Arc<Self>) -> Option<std::sync::Arc<Self>> {
316 Some(self)
317 }
318 fn create_kernel(self: std::sync::Arc<Self>) -> Box<dyn crate::kernel::Kernel> {
319 Box::new(PolydatKernel::from_program(self))
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use crate::dsl::compile::compile_polydat;
327
328 #[test]
330 fn dataflow_indexed_set_get() {
331 let mut k = compile_polydat("input cycle: u64\nconst x := 7\n").unwrap();
332 k.set_wire(0_usize, Value::U64(42)).expect("typed write");
334 assert_eq!(k.get_wire(0_usize), Some(Value::U64(42)));
335 }
336
337 #[test]
339 fn dataflow_named_set_get() {
340 let mut k = compile_polydat("input cycle: u64\nextern n: u64\n").unwrap();
341 k.set_wire("n", Value::U64(5)).expect("typed write");
342 match k.get_wire("n") {
343 Some(Value::U64(5)) => {}
344 other => panic!("expected U64(5), got {other:?}"),
345 }
346 }
347
348 #[test]
350 fn dataflow_string_key() {
351 let mut k = compile_polydat("input cycle: u64\nextern n: u64\n").unwrap();
352 let name = String::from("n");
353 k.set_wire(&name, Value::U64(99)).expect("typed write");
354 assert_eq!(k.get_wire(name.clone()), Some(Value::U64(99)));
355 }
356
357 #[test]
359 fn dataflow_unknown_name_safe() {
360 let mut k = compile_polydat("input cycle: u64\n").unwrap();
361 let err = k.set_wire("nonexistent", Value::U64(1)).unwrap_err();
362 assert!(matches!(
363 err,
364 crate::kernel::api::WriteError::UnknownWire { .. }
365 ));
366 assert!(k.get_wire("nonexistent").is_none());
367 }
368
369 #[test]
378 fn dataflow_type_mismatch_rejected() {
379 let mut k = compile_polydat("input cycle: u64\nextern n: u64\n").unwrap();
380 let err = k
381 .set_wire(
382 "n",
383 Value::VecF32(crate::ast::SliceArc::from_vec(vec![1.0_f32, 2.0])),
384 )
385 .unwrap_err();
386 match err {
387 crate::kernel::api::WriteError::TypeMismatch {
388 slot,
389 expected,
390 got,
391 } => {
392 assert_eq!(slot, "n");
393 assert_eq!(expected, PortType::U64);
394 assert_eq!(got, PortType::VecF32);
395 }
396 other => panic!("expected TypeMismatch, got {other:?}"),
397 }
398 }
399
400 #[test]
405 fn vec_to_scalar_diagnostic_mentions_explicit_helpers() {
406 let err = crate::kernel::api::WriteError::TypeMismatch {
407 slot: "score".into(),
408 expected: PortType::F64,
409 got: PortType::VecF32,
410 };
411 let msg = err.to_string();
412 assert!(msg.contains("vec_len"), "missing vec_len hint: {msg}");
413 assert!(msg.contains("vec_first"), "missing vec_first hint: {msg}");
414 }
415
416 #[test]
419 fn dataflow_healable_mismatch_adapts() {
420 let mut k = compile_polydat("input cycle: u64\nextern x: f64\n").unwrap();
421 k.set_wire("x", Value::U64(42))
424 .expect("u64→f64 boundary adapter");
425 match k.get_wire("x") {
426 Some(Value::F64(42.0)) => {}
427 other => panic!("expected adapted F64(42.0), got {other:?}"),
428 }
429 }
430
431 #[test]
435 fn dataflow_none_passes_through_any_slot() {
436 let mut k = compile_polydat("input cycle: u64\nextern n: u64\n").unwrap();
437 k.set_wire("n", Value::None).expect("None always permitted");
438 }
439
440 #[test]
442 fn metadata_listings() {
443 let k = compile_polydat("input (cycle: u64, thread: u64)\nextern n: u64\nconst x := 7\n")
444 .unwrap();
445 let inputs: Vec<String> = k.input_names();
446 assert!(inputs.iter().any(|s| s == "cycle"));
447 assert!(inputs.iter().any(|s| s == "n"));
448 assert_eq!(k.coord_count(), 2); assert!(k.find_input("n").is_some());
450 assert_eq!(k.input_port_type("n"), Some(PortType::U64));
451 }
452
453 #[test]
457 fn construction_symmetric_paths() {
458 let root_opts = crate::kernel::subcontext::CompileOptions {
459 workload_dir: None,
460 polydat_lib_paths: Vec::new(),
461 strict: false,
462 required_outputs: Vec::new(),
463 context_label: Some("root".to_string()),
464 cursor_limit: None,
465 ..Default::default()
466 };
467 let root_matter = crate::kernel::subcontext::PolydatMatter::builder()
468 .label("root")
469 .source("input cycle: u64\nshared flag := 0\n")
470 .options(root_opts)
471 .build()
472 .expect("matter build");
473 let root =
474 <PolydatKernel as Construction>::root(root_matter).expect("root from source matter");
475
476 let sub_opts = crate::kernel::subcontext::CompileOptions {
477 workload_dir: None,
478 polydat_lib_paths: Vec::new(),
479 strict: false,
480 required_outputs: Vec::new(),
481 context_label: Some("sub".to_string()),
482 cursor_limit: None,
483 ..Default::default()
484 };
485 let sub_matter = crate::kernel::subcontext::PolydatMatter::builder()
486 .label("sub")
487 .source("input cycle: u64\n")
488 .options(sub_opts)
489 .build()
490 .expect("matter build");
491 let _sub = root
492 .subscope(sub_matter)
493 .expect("subscope from source matter");
494 }
495
496 #[test]
500 fn construction_root_from_program() {
501 let template = compile_polydat("input cycle: u64\nextern n: u64\n").unwrap();
502 let program = template.program().clone();
503 let matter = crate::kernel::subcontext::PolydatMatter::builder()
504 .program(program)
505 .build()
506 .expect("matter build");
507 let mut root =
508 <PolydatKernel as Construction>::root(matter).expect("root from program matter");
509 root.set_wire("n", Value::U64(13)).expect("set_wire");
510 assert_eq!(root.get_wire("n"), Some(Value::U64(13)));
511 }
512
513 #[test]
515 fn builder_rejects_multiple_forms() {
516 let template = compile_polydat("input cycle: u64\n").unwrap();
517 match crate::kernel::subcontext::PolydatMatter::builder()
518 .source("input cycle: u64\n")
519 .program(template.program().clone())
520 .build()
521 {
522 Err(msg) => assert!(
523 msg.contains("multiple"),
524 "expected multiple-forms error, got: {msg}"
525 ),
526 Ok(_) => panic!("multiple forms must error"),
527 }
528 }
529
530 #[test]
532 fn builder_rejects_empty() {
533 match crate::kernel::subcontext::PolydatMatter::builder().build() {
534 Err(msg) => assert!(
535 msg.contains("no input form"),
536 "expected no-form error, got: {msg}"
537 ),
538 Ok(_) => panic!("empty matter must error"),
539 }
540 }
541}