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