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 known: Vec::new(),
72 });
73 }
74 };
75
76 let slot_name = self
77 .program()
78 .input_name_by_idx(idx)
79 .map(|s| s.to_string())
80 .unwrap_or_else(|| format!("wire[{idx}]"));
81
82 let got = value.port_type();
92 let adapted = crate::kernel::state::adapt_boundary_value(&slot_name, slot_type, value);
93 if !adapted.satisfies_slot(slot_type) {
104 return Err(WriteError::TypeMismatch {
105 slot: slot_name,
106 expected: slot_type,
107 got,
108 });
109 }
110 self.state().set_input(idx, adapted);
111 Ok(())
112 }
113
114 #[inline]
115 fn get_wire_idx(&self, idx: usize) -> Value {
116 self.state_ref().get_input(idx)
117 }
118}
119
120impl Construction for PolydatKernel {
121 type Error = crate::kernel::subcontext::ContractViolation;
122
123 fn root(matter: crate::kernel::subcontext::PolydatMatter<'_>) -> Result<Self, Self::Error> {
124 use crate::kernel::subcontext::PolydatMatterInner;
125 match matter.inner {
126 PolydatMatterInner::Source(s) => {
127 let options = crate::dsl::compile::CompileOptions {
128 source_dir: s.options.workload_dir.clone(),
129 lib_paths: s.options.polydat_lib_paths,
130 required_outputs: s.options.required_outputs.clone(),
131 strict: s.options.strict,
132 context: s
133 .options
134 .context_label
135 .clone()
136 .unwrap_or_else(|| s.label.clone()),
137 cursor_limit: s.options.cursor_limit,
138 ledger: None,
139 engine: crate::Engine::default(),
140 };
141 crate::dsl::compile::compile_polydat_interpreter_with_options(
142 &s.body, &options, None,
143 )
144 .map_err(|e| crate::kernel::subcontext::ContractViolation::Compile(e.to_string()))
145 }
146 PolydatMatterInner::Statements(s) => {
147 let file = crate::dsl::ast::PolydatFile {
152 statements: s.statements,
153 };
154 let options = crate::dsl::compile::CompileOptions {
155 source_dir: s.options.workload_dir.clone(),
156 lib_paths: s.options.polydat_lib_paths,
157 required_outputs: s.options.required_outputs.clone(),
158 strict: s.options.strict,
159 context: s
160 .options
161 .context_label
162 .clone()
163 .unwrap_or_else(|| s.label.clone()),
164 cursor_limit: None,
165 ledger: None,
166 engine: crate::Engine::default(),
167 };
168 crate::dsl::compile::compile_ast_interpreter_with_options(&file, "", &options, None)
169 .map_err(|e| {
170 crate::kernel::subcontext::ContractViolation::Compile(e.to_string())
171 })
172 }
173 PolydatMatterInner::Program(p) => {
174 let mut k = PolydatKernel::from_program(p.program);
175 for (var, value) in p.iter_bindings {
176 if let Some(idx) = k.program().find_input(var) {
177 k.state().set_input(idx, value.clone());
178 }
179 }
180 Ok(k)
181 }
182 }
183 }
184
185 fn subscope(
186 &self,
187 matter: crate::kernel::subcontext::PolydatMatter<'_>,
188 ) -> Result<Self, Self::Error> {
189 PolydatKernel::build_subscope(self, matter)
191 }
192}
193
194impl crate::kernel::Kernel for PolydatKernel {
197 fn engine(&self) -> crate::compile::select::Engine {
198 crate::compile::select::Engine::Interpreter(self.program().cone_mode())
199 }
200 fn set_inputs(&mut self, coords: &[u64]) {
201 PolydatKernel::set_inputs(self, coords);
202 }
203 fn set_input(&mut self, name: &str, value: Value) -> Result<(), crate::kernel::WriteError> {
204 PolydatKernel::set_input(self, name, value)
205 }
206 fn set_cursor(
207 &mut self,
208 name: &str,
209 partition: &crate::iteration::cursor_partition::Partition,
210 ) -> Result<(), crate::kernel::WriteError> {
211 PolydatKernel::set_cursor(self, name, partition)
212 }
213 fn eval(&mut self) {
216 for name in Metadata::output_names(self) {
217 let _ = PolydatKernel::pull_ref(self, &name);
218 }
219 }
220 fn pull(&mut self, name: &str) -> Value {
221 PolydatKernel::pull_ref(self, name).clone()
222 }
223 fn input_names(&self) -> Vec<String> {
224 Metadata::input_names(self)
225 }
226 fn output_names(&self) -> Vec<String> {
227 Metadata::output_names(self)
228 }
229 fn output_type(&self, name: &str) -> Option<PortType> {
230 Metadata::output_port_type(self, name)
231 }
232 fn externs(&self) -> Vec<(String, PortType)> {
233 let program = self.program();
234 Metadata::input_names(self)
235 .into_iter()
236 .enumerate()
237 .filter(|(i, _)| program.input_kind(*i) != Some(crate::kernel::InputKind::Coordinate))
238 .filter_map(|(i, name)| Metadata::input_port_type_by_idx(self, i).map(|t| (name, t)))
239 .collect()
240 }
241 fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
242 self.program().cursor_schemas()
243 }
244 fn input_value(&self, name: &str) -> Option<Value> {
245 let idx = self.program().find_input(name)?;
246 Some(self.state_ref().get_input(idx))
247 }
248 fn input_index(&self, name: &str) -> Option<usize> {
249 self.program().find_input(name)
250 }
251 fn set_input_at(
252 &mut self,
253 index: usize,
254 value: Value,
255 ) -> Result<(), crate::kernel::WriteError> {
256 PolydatKernel::set_input_at(self, index, value)
257 }
258 fn output_index(&self, name: &str) -> Option<usize> {
259 self.program().output_index(name)
260 }
261 fn pull_at(&mut self, index: usize) -> Value {
262 PolydatKernel::pull_ref_at(self, index).clone()
263 }
264 fn traversals(&self) -> &[crate::dsl::traversal::Traversal] {
265 self.program().traversals()
266 }
267 fn plan(&self) -> crate::EnginePlan {
268 self.program().engine_plan()
269 }
270 fn traverse(&mut self, index: usize) -> Result<crate::kernel::TraversalStream, String> {
271 PolydatKernel::traverse(self, index)
272 }
273 fn invalidate_all(&mut self) {
274 self.state().invalidate_all();
275 }
276 fn shared_cells(&self) -> Vec<crate::kernel::SharedCellEntry> {
277 self.shared_cells_in_scope()
278 }
279 fn output_cell(&self, name: &str) -> Option<crate::kernel::SharedCell> {
280 self.state_ref().core.output_cell(self.program(), name)
282 }
283 fn output_modifier(&self, name: &str) -> crate::dsl::ast::BindingModifier {
284 self.program().output_modifier(name)
285 }
286 fn cells_in_scope(&self) -> Vec<crate::kernel::SharedCellEntry> {
287 self.shared_cells_in_scope()
290 }
291 fn set_transit_cells(&mut self, cells: Vec<crate::kernel::SharedCellEntry>) {
292 self.replace_transit_cells(cells);
293 }
294 fn scope_coordinates(&self) -> &[crate::kernel::ScopeCoord] {
295 PolydatKernel::scope_coordinates(self)
296 }
297 fn extend_scope_coordinates(&mut self, outer: &[crate::kernel::ScopeCoord]) {
298 PolydatKernel::extend_scope_coordinates(self, outer);
299 }
300 fn input_port_type(&self, name: &str) -> Option<crate::ast::PortType> {
301 self.program().input_port_type(name)
302 }
303 fn bind_input_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> bool {
304 let Some(idx) = self.program().find_input(name) else {
309 return false;
310 };
311 self.state().attach_shared_cell(idx, cell);
312 true
313 }
314 fn attach_shared_cell(
315 &mut self,
316 name: &str,
317 cell: crate::kernel::SharedCell,
318 ) -> Result<(), String> {
319 let program = self.program().clone();
320 let shared = program.shared_outputs();
321 let idx = program.find_input(name).filter(|_| shared.contains(&name));
322 let Some(idx) = idx else {
323 return Err(format!(
324 "no `shared` binding named '{name}'; this kernel's shared bindings are {shared:?}"
325 ));
326 };
327 self.state().attach_shared_cell(idx, cell);
328 Ok(())
329 }
330 fn into_program(self: Box<Self>) -> std::sync::Arc<dyn crate::kernel::KernelProgram> {
331 PolydatKernel::into_program(*self)
332 }
333 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
334 self.program().ledger()
335 }
336}
337
338impl crate::kernel::KernelInternals for PolydatKernel {
339 fn set_traversals(
340 &mut self,
341 traversals: Vec<crate::dsl::traversal::Traversal>,
342 producers: Vec<crate::dsl::traversal::Producer>,
343 ) {
344 PolydatKernel::set_traversals(self, traversals, producers);
345 }
346 fn folded_value(&self, name: &str) -> Option<Value> {
347 self.get_constant(name).cloned()
348 }
349 fn set_cursor_extent(&mut self, index: usize, extent: u64) {
350 let mut schemas = self.program().cursor_schemas().to_vec();
351 if let Some(schema) = schemas.get_mut(index) {
352 schema.extent = Some(extent);
353 self.set_cursor_schemas(schemas);
354 }
355 }
356}
357
358impl crate::kernel::KernelProgram for crate::kernel::PolydatProgram {
359 fn engine(&self) -> crate::compile::select::Engine {
360 crate::compile::select::Engine::Interpreter(self.cone_mode())
361 }
362 fn as_interpreter(self: std::sync::Arc<Self>) -> Option<std::sync::Arc<Self>> {
363 Some(self)
364 }
365 fn create_kernel(self: std::sync::Arc<Self>) -> Box<dyn crate::kernel::Kernel> {
366 Box::new(PolydatKernel::from_program(self))
367 }
368 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
369 crate::kernel::PolydatProgram::ledger(self)
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use crate::dsl::compile::compile_polydat_interpreter;
377
378 #[test]
380 fn dataflow_indexed_set_get() {
381 let mut k = compile_polydat_interpreter("input cycle: u64\nconst x := 7\n").unwrap();
382 k.set_wire(0_usize, Value::U64(42)).expect("typed write");
384 assert_eq!(k.get_wire(0_usize), Some(Value::U64(42)));
385 }
386
387 #[test]
389 fn dataflow_named_set_get() {
390 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
391 k.set_wire("n", Value::U64(5)).expect("typed write");
392 match k.get_wire("n") {
393 Some(Value::U64(5)) => {}
394 other => panic!("expected U64(5), got {other:?}"),
395 }
396 }
397
398 #[test]
400 fn dataflow_string_key() {
401 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
402 let name = String::from("n");
403 k.set_wire(&name, Value::U64(99)).expect("typed write");
404 assert_eq!(k.get_wire(name.clone()), Some(Value::U64(99)));
405 }
406
407 #[test]
409 fn dataflow_unknown_name_safe() {
410 let mut k = compile_polydat_interpreter("input cycle: u64\n").unwrap();
411 let err = k.set_wire("nonexistent", Value::U64(1)).unwrap_err();
412 assert!(matches!(
413 err,
414 crate::kernel::api::WriteError::UnknownWire { .. }
415 ));
416 assert!(k.get_wire("nonexistent").is_none());
417 }
418
419 #[test]
428 fn dataflow_type_mismatch_rejected() {
429 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
430 let err = k
431 .set_wire(
432 "n",
433 Value::VecF32(crate::ast::SliceArc::from_vec(vec![1.0_f32, 2.0])),
434 )
435 .unwrap_err();
436 match err {
437 crate::kernel::api::WriteError::TypeMismatch {
438 slot,
439 expected,
440 got,
441 } => {
442 assert_eq!(slot, "n");
443 assert_eq!(expected, PortType::U64);
444 assert_eq!(got, PortType::VecF32);
445 }
446 other => panic!("expected TypeMismatch, got {other:?}"),
447 }
448 }
449
450 #[test]
455 fn vec_to_scalar_diagnostic_mentions_explicit_helpers() {
456 let err = crate::kernel::api::WriteError::TypeMismatch {
457 slot: "score".into(),
458 expected: PortType::F64,
459 got: PortType::VecF32,
460 };
461 let msg = err.to_string();
462 assert!(
463 msg.contains("reduction node"),
464 "missing reduction hint: {msg}"
465 );
466 assert!(msg.contains("vec_dot"), "missing vec_dot hint: {msg}");
467 }
468
469 #[test]
472 fn dataflow_healable_mismatch_adapts() {
473 let mut k = compile_polydat_interpreter("input cycle: u64\nextern x: f64\n").unwrap();
474 k.set_wire("x", Value::U64(42))
477 .expect("u64→f64 boundary adapter");
478 match k.get_wire("x") {
479 Some(Value::F64(42.0)) => {}
480 other => panic!("expected adapted F64(42.0), got {other:?}"),
481 }
482 }
483
484 #[test]
488 fn dataflow_none_passes_through_any_slot() {
489 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
490 k.set_wire("n", Value::None).expect("None always permitted");
491 }
492
493 #[test]
495 fn metadata_listings() {
496 let k = compile_polydat_interpreter(
497 "input (cycle: u64, thread: u64)\nextern n: u64\nconst x := 7\n",
498 )
499 .unwrap();
500 let inputs: Vec<String> = k.input_names();
501 assert!(inputs.iter().any(|s| s == "cycle"));
502 assert!(inputs.iter().any(|s| s == "n"));
503 assert_eq!(k.coord_count(), 2); assert!(k.find_input("n").is_some());
505 assert_eq!(k.input_port_type("n"), Some(PortType::U64));
506 }
507
508 #[test]
512 fn construction_symmetric_paths() {
513 let root_opts = crate::kernel::subcontext::CompileOptions {
514 workload_dir: None,
515 polydat_lib_paths: Vec::new(),
516 strict: false,
517 required_outputs: Vec::new(),
518 context_label: Some("root".to_string()),
519 cursor_limit: None,
520 ..Default::default()
521 };
522 let root_matter = crate::kernel::subcontext::PolydatMatter::builder()
523 .label("root")
524 .source("input cycle: u64\nshared flag := 0\n")
525 .options(root_opts)
526 .build()
527 .expect("matter build");
528 let root =
529 <PolydatKernel as Construction>::root(root_matter).expect("root from source matter");
530
531 let sub_opts = crate::kernel::subcontext::CompileOptions {
532 workload_dir: None,
533 polydat_lib_paths: Vec::new(),
534 strict: false,
535 required_outputs: Vec::new(),
536 context_label: Some("sub".to_string()),
537 cursor_limit: None,
538 ..Default::default()
539 };
540 let sub_matter = crate::kernel::subcontext::PolydatMatter::builder()
541 .label("sub")
542 .source("input cycle: u64\n")
543 .options(sub_opts)
544 .build()
545 .expect("matter build");
546 let _sub = root
547 .subscope(sub_matter)
548 .expect("subscope from source matter");
549 }
550
551 #[test]
555 fn construction_root_from_program() {
556 let template = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
557 let program = template.program().clone();
558 let matter = crate::kernel::subcontext::PolydatMatter::builder()
559 .program(program)
560 .build()
561 .expect("matter build");
562 let mut root =
563 <PolydatKernel as Construction>::root(matter).expect("root from program matter");
564 root.set_wire("n", Value::U64(13)).expect("set_wire");
565 assert_eq!(root.get_wire("n"), Some(Value::U64(13)));
566 }
567
568 #[test]
570 fn builder_rejects_multiple_forms() {
571 let template = compile_polydat_interpreter("input cycle: u64\n").unwrap();
572 match crate::kernel::subcontext::PolydatMatter::builder()
573 .source("input cycle: u64\n")
574 .program(template.program().clone())
575 .build()
576 {
577 Err(msg) => assert!(
578 msg.contains("multiple"),
579 "expected multiple-forms error, got: {msg}"
580 ),
581 Ok(_) => panic!("multiple forms must error"),
582 }
583 }
584
585 #[test]
587 fn builder_rejects_empty() {
588 match crate::kernel::subcontext::PolydatMatter::builder().build() {
589 Err(msg) => assert!(
590 msg.contains("no input form"),
591 "expected no-form error, got: {msg}"
592 ),
593 Ok(_) => panic!("empty matter must error"),
594 }
595 }
596}