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 input_variance: s.options.input_variance,
139 inferred_externs: Vec::new(),
140 ledger: None,
141 engine: crate::Engine::default(),
142 };
143 crate::dsl::compile::compile_polydat_interpreter_with_options(
144 &s.body, &options, None,
145 )
146 .map_err(|e| crate::kernel::subcontext::ContractViolation::Compile(e.to_string()))
147 }
148 PolydatMatterInner::Statements(s) => {
149 let file = crate::dsl::ast::PolydatFile {
154 statements: s.statements,
155 };
156 let options = crate::dsl::compile::CompileOptions {
157 source_dir: s.options.workload_dir.clone(),
158 lib_paths: s.options.polydat_lib_paths,
159 required_outputs: s.options.required_outputs.clone(),
160 strict: s.options.strict,
161 context: s
162 .options
163 .context_label
164 .clone()
165 .unwrap_or_else(|| s.label.clone()),
166 cursor_limit: None,
167 input_variance: s.options.input_variance,
168 inferred_externs: Vec::new(),
169 ledger: None,
170 engine: crate::Engine::default(),
171 };
172 crate::dsl::compile::compile_ast_interpreter_with_options(&file, "", &options, None)
173 .map_err(|e| {
174 crate::kernel::subcontext::ContractViolation::Compile(e.to_string())
175 })
176 }
177 PolydatMatterInner::Program(p) => {
178 let mut k = PolydatKernel::from_program(p.program);
179 for (var, value) in p.iter_bindings {
180 if let Some(idx) = k.program().find_input(var) {
181 k.state().set_input(idx, value.clone());
182 }
183 }
184 Ok(k)
185 }
186 }
187 }
188
189 fn subscope(
190 &self,
191 matter: crate::kernel::subcontext::PolydatMatter<'_>,
192 ) -> Result<Self, Self::Error> {
193 PolydatKernel::build_subscope(self, matter)
195 }
196}
197
198impl crate::kernel::Kernel for PolydatKernel {
201 fn engine(&self) -> crate::compile::select::Engine {
202 crate::compile::select::Engine::Interpreter(self.program().cone_mode())
203 }
204 fn set_inputs(&mut self, coords: &[u64]) {
205 PolydatKernel::set_inputs(self, coords);
206 }
207 fn set_input(&mut self, name: &str, value: Value) -> Result<(), crate::kernel::WriteError> {
208 PolydatKernel::set_input(self, name, value)
209 }
210 fn set_cursor(
211 &mut self,
212 name: &str,
213 partition: &crate::iteration::cursor_partition::Partition,
214 ) -> Result<(), crate::kernel::WriteError> {
215 PolydatKernel::set_cursor(self, name, partition)
216 }
217 fn eval(&mut self) {
220 PolydatKernel::eval_read(self);
221 }
222 fn pull(&mut self, name: &str) -> Value {
223 PolydatKernel::pull_ref(self, name).clone()
224 }
225 fn input_names(&self) -> Vec<String> {
226 Metadata::input_names(self)
227 }
228 fn output_names(&self) -> Vec<String> {
229 Metadata::output_names(self)
230 }
231 fn output_type(&self, name: &str) -> Option<PortType> {
232 Metadata::output_port_type(self, name)
233 }
234 fn externs(&self) -> Vec<(String, PortType)> {
235 let program = self.program();
236 Metadata::input_names(self)
237 .into_iter()
238 .enumerate()
239 .filter(|(i, _)| program.input_kind(*i) != Some(crate::kernel::InputKind::Coordinate))
240 .filter_map(|(i, name)| Metadata::input_port_type_by_idx(self, i).map(|t| (name, t)))
241 .collect()
242 }
243 fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
244 self.program().cursor_schemas()
245 }
246 fn input_value(&self, name: &str) -> Option<Value> {
247 let idx = self.program().find_input(name)?;
248 Some(self.state_ref().get_input(idx))
249 }
250 fn input_index(&self, name: &str) -> Option<usize> {
251 self.program().find_input(name)
252 }
253 fn set_input_at(
254 &mut self,
255 index: usize,
256 value: Value,
257 ) -> Result<(), crate::kernel::WriteError> {
258 PolydatKernel::set_input_at(self, index, value)
259 }
260 fn output_index(&self, name: &str) -> Option<usize> {
261 self.program().output_index(name)
262 }
263 fn const_inits(&self) -> &[crate::kernel::ConstInit] {
264 self.program().const_inits()
265 }
266 fn init_input_at(
267 &mut self,
268 index: usize,
269 value: Value,
270 ) -> Result<(), crate::kernel::WriteError> {
271 PolydatKernel::init_input_at(self, index, value)
272 }
273 fn pull_at(&mut self, index: usize) -> Value {
274 PolydatKernel::pull_ref_at(self, index).clone()
275 }
276 fn traversals(&self) -> &[crate::dsl::traversal::Traversal] {
277 self.program().traversals()
278 }
279 fn plan(&self) -> crate::EnginePlan {
280 self.program().engine_plan()
281 }
282 fn traverse(&mut self, index: usize) -> Result<crate::kernel::TraversalStream, String> {
283 PolydatKernel::traverse(self, index)
284 }
285 fn invalidate_all(&mut self) {
286 self.state().invalidate_all();
287 }
288 fn shared_cells(&self) -> Vec<crate::kernel::SharedCellEntry> {
289 self.shared_cells_in_scope()
290 }
291 fn output_cell(&self, name: &str) -> Option<crate::kernel::SharedCell> {
292 self.state_ref().core.output_cell(self.program(), name)
294 }
295 fn output_modifier(&self, name: &str) -> crate::dsl::ast::BindingModifier {
296 self.program().output_modifier(name)
297 }
298 fn cells_in_scope(&self) -> Vec<crate::kernel::SharedCellEntry> {
299 self.shared_cells_in_scope()
302 }
303 fn set_transit_cells(&mut self, cells: Vec<crate::kernel::SharedCellEntry>) {
304 self.replace_transit_cells(cells);
305 }
306 fn scope_coordinates(&self) -> &[crate::kernel::ScopeCoord] {
307 PolydatKernel::scope_coordinates(self)
308 }
309 fn extend_scope_coordinates(&mut self, outer: &[crate::kernel::ScopeCoord]) {
310 PolydatKernel::extend_scope_coordinates(self, outer);
311 }
312 fn input_port_type(&self, name: &str) -> Option<crate::ast::PortType> {
313 self.program().input_port_type(name)
314 }
315 fn bind_input_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> bool {
316 let Some(idx) = self.program().find_input(name) else {
321 return false;
322 };
323 self.state().attach_shared_cell(idx, cell);
324 true
325 }
326 fn attach_shared_cell(
327 &mut self,
328 name: &str,
329 cell: crate::kernel::SharedCell,
330 ) -> Result<(), String> {
331 let program = self.program().clone();
332 let shared = program.shared_outputs();
333 let idx = program.find_input(name).filter(|_| shared.contains(&name));
334 let Some(idx) = idx else {
335 return Err(format!(
336 "no `shared` binding named '{name}'; this kernel's shared bindings are {shared:?}"
337 ));
338 };
339 self.state().attach_shared_cell(idx, cell);
340 Ok(())
341 }
342 fn into_program(self: Box<Self>) -> std::sync::Arc<dyn crate::kernel::KernelProgram> {
343 PolydatKernel::into_program(*self)
344 }
345 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
346 self.program().ledger()
347 }
348 fn coord_count(&self) -> usize {
349 self.program().coord_count()
350 }
351 fn input_value_at(&self, index: usize) -> Option<Value> {
352 (index < self.state_ref().core.inputs.len())
353 .then(|| self.state_ref().read_input_value(index))
354 }
355 fn input_default_at(&self, index: usize) -> Option<Value> {
356 self.program().input_default_by_idx(index).cloned()
357 }
358 fn input_is_cell_bound(&self, index: usize) -> bool {
359 self.state_ref().shared_cell(index).is_some()
360 }
361 fn reset_inputs(&mut self) {
362 let from = self.program().coord_count();
363 self.state().reset_inputs_from(from);
364 }
365 fn fork(&self) -> Box<dyn crate::kernel::Kernel> {
366 Box::new(self.fork_kernel())
367 }
368 fn publish_broadcasts(&mut self) {
369 self.advance_broadcasts();
370 }
371 fn commit_write_throughs(&mut self) -> Result<(), String> {
372 PolydatKernel::commit_write_throughs(self)
373 }
374 fn program_id(&self) -> crate::kernel::ProgramId {
375 crate::kernel::ProgramId(std::sync::Arc::as_ptr(self.program()) as *const () as usize)
376 }
377 fn input_type_origin(&self, name: &str) -> Option<crate::kernel::TypeOrigin> {
378 self.program().input_type_origin(name)
379 }
380}
381
382impl crate::kernel::KernelInternals for PolydatKernel {
383 fn set_write_throughs(&mut self, pairs: Vec<(String, String)>) {
384 PolydatKernel::set_write_throughs(
385 self,
386 pairs
387 .into_iter()
388 .map(
389 |(export_name, source_output)| crate::kernel::KernelWriteThrough {
390 export_name,
391 source_output,
392 },
393 )
394 .collect(),
395 );
396 }
397 fn set_traversals(
398 &mut self,
399 traversals: Vec<crate::dsl::traversal::Traversal>,
400 producers: Vec<crate::dsl::traversal::Producer>,
401 ) {
402 PolydatKernel::set_traversals(self, traversals, producers);
403 }
404 fn folded_value(&self, name: &str) -> Option<Value> {
407 if !self.program().is_fixed_output(name) {
408 return None;
409 }
410 self.get_constant(name).cloned()
411 }
412 fn set_cursor_extent(&mut self, index: usize, extent: u64) {
413 let mut schemas = self.program().cursor_schemas().to_vec();
414 if let Some(schema) = schemas.get_mut(index) {
415 schema.extent = Some(extent);
416 self.set_cursor_schemas(schemas);
417 }
418 }
419}
420
421impl crate::kernel::KernelProgram for crate::kernel::PolydatProgram {
422 fn engine(&self) -> crate::compile::select::Engine {
423 crate::compile::select::Engine::Interpreter(self.cone_mode())
424 }
425 fn as_interpreter(self: std::sync::Arc<Self>) -> Option<std::sync::Arc<Self>> {
426 Some(self)
427 }
428 fn create_uninitialized(self: std::sync::Arc<Self>) -> Box<dyn crate::kernel::Kernel> {
429 Box::new(PolydatKernel::from_program(self))
430 }
431 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
432 crate::kernel::PolydatProgram::ledger(self)
433 }
434 fn program_id(&self) -> crate::kernel::ProgramId {
435 crate::kernel::ProgramId(self as *const Self as *const () as usize)
436 }
437}
438
439#[cfg(test)]
440#[allow(deprecated)]
442mod tests {
443 use super::*;
444 use crate::dsl::compile::compile_polydat_interpreter;
445
446 #[test]
448 fn dataflow_indexed_set_get() {
449 let mut k = compile_polydat_interpreter("input cycle: u64\nconst x := 7\n").unwrap();
450 k.set_wire(0_usize, Value::U64(42)).expect("typed write");
452 assert_eq!(k.get_wire(0_usize), Some(Value::U64(42)));
453 }
454
455 #[test]
457 fn dataflow_named_set_get() {
458 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
459 k.set_wire("n", Value::U64(5)).expect("typed write");
460 match k.get_wire("n") {
461 Some(Value::U64(5)) => {}
462 other => panic!("expected U64(5), got {other:?}"),
463 }
464 }
465
466 #[test]
468 fn dataflow_string_key() {
469 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
470 let name = String::from("n");
471 k.set_wire(&name, Value::U64(99)).expect("typed write");
472 assert_eq!(k.get_wire(name.clone()), Some(Value::U64(99)));
473 }
474
475 #[test]
477 fn dataflow_unknown_name_safe() {
478 let mut k = compile_polydat_interpreter("input cycle: u64\n").unwrap();
479 let err = k.set_wire("nonexistent", Value::U64(1)).unwrap_err();
480 assert!(matches!(
481 err,
482 crate::kernel::api::WriteError::UnknownWire { .. }
483 ));
484 assert!(k.get_wire("nonexistent").is_none());
485 }
486
487 #[test]
496 fn dataflow_type_mismatch_rejected() {
497 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
498 let err = k
499 .set_wire(
500 "n",
501 Value::VecF32(crate::ast::SliceArc::from_vec(vec![1.0_f32, 2.0])),
502 )
503 .unwrap_err();
504 match err {
505 crate::kernel::api::WriteError::TypeMismatch {
506 slot,
507 expected,
508 got,
509 } => {
510 assert_eq!(slot, "n");
511 assert_eq!(expected, PortType::U64);
512 assert_eq!(got, PortType::VecF32);
513 }
514 other => panic!("expected TypeMismatch, got {other:?}"),
515 }
516 }
517
518 #[test]
523 fn vec_to_scalar_diagnostic_mentions_explicit_helpers() {
524 let err = crate::kernel::api::WriteError::TypeMismatch {
525 slot: "score".into(),
526 expected: PortType::F64,
527 got: PortType::VecF32,
528 };
529 let msg = err.to_string();
530 assert!(
531 msg.contains("reduction node"),
532 "missing reduction hint: {msg}"
533 );
534 assert!(msg.contains("vec_dot"), "missing vec_dot hint: {msg}");
535 }
536
537 #[test]
540 fn dataflow_healable_mismatch_adapts() {
541 let mut k = compile_polydat_interpreter("input cycle: u64\nextern x: f64\n").unwrap();
542 k.set_wire("x", Value::U64(42))
545 .expect("u64→f64 boundary adapter");
546 match k.get_wire("x") {
547 Some(Value::F64(42.0)) => {}
548 other => panic!("expected adapted F64(42.0), got {other:?}"),
549 }
550 }
551
552 #[test]
556 fn dataflow_none_passes_through_any_slot() {
557 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
558 k.set_wire("n", Value::None).expect("None always permitted");
559 }
560
561 #[test]
563 fn metadata_listings() {
564 let k = compile_polydat_interpreter(
565 "input (cycle: u64, thread: u64)\nextern n: u64\nconst x := 7\n",
566 )
567 .unwrap();
568 let inputs: Vec<String> = k.input_names();
569 assert!(inputs.iter().any(|s| s == "cycle"));
570 assert!(inputs.iter().any(|s| s == "n"));
571 assert_eq!(k.coord_count(), 2); assert!(k.find_input("n").is_some());
573 assert_eq!(k.input_port_type("n"), Some(PortType::U64));
574 }
575
576 #[test]
580 fn construction_symmetric_paths() {
581 let root_opts = crate::kernel::subcontext::CompileOptions {
582 workload_dir: None,
583 polydat_lib_paths: Vec::new(),
584 strict: false,
585 required_outputs: Vec::new(),
586 context_label: Some("root".to_string()),
587 cursor_limit: None,
588 ..Default::default()
589 };
590 let root_matter = crate::kernel::subcontext::PolydatMatter::builder()
591 .label("root")
592 .source("input cycle: u64\nshared flag := 0\n")
593 .options(root_opts)
594 .build()
595 .expect("matter build");
596 let root =
597 <PolydatKernel as Construction>::root(root_matter).expect("root from source matter");
598
599 let sub_opts = crate::kernel::subcontext::CompileOptions {
600 workload_dir: None,
601 polydat_lib_paths: Vec::new(),
602 strict: false,
603 required_outputs: Vec::new(),
604 context_label: Some("sub".to_string()),
605 cursor_limit: None,
606 ..Default::default()
607 };
608 let sub_matter = crate::kernel::subcontext::PolydatMatter::builder()
609 .label("sub")
610 .source("input cycle: u64\n")
611 .options(sub_opts)
612 .build()
613 .expect("matter build");
614 let _sub = root
615 .subscope(sub_matter)
616 .expect("subscope from source matter");
617 }
618
619 #[test]
623 fn construction_root_from_program() {
624 let template = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
625 let program = template.program().clone();
626 let matter = crate::kernel::subcontext::PolydatMatter::builder()
627 .program(program)
628 .build()
629 .expect("matter build");
630 let mut root =
631 <PolydatKernel as Construction>::root(matter).expect("root from program matter");
632 root.set_wire("n", Value::U64(13)).expect("set_wire");
633 assert_eq!(root.get_wire("n"), Some(Value::U64(13)));
634 }
635
636 #[test]
638 fn builder_rejects_multiple_forms() {
639 let template = compile_polydat_interpreter("input cycle: u64\n").unwrap();
640 match crate::kernel::subcontext::PolydatMatter::builder()
641 .source("input cycle: u64\n")
642 .program(template.program().clone())
643 .build()
644 {
645 Err(msg) => assert!(
646 msg.contains("multiple"),
647 "expected multiple-forms error, got: {msg}"
648 ),
649 Ok(_) => panic!("multiple forms must error"),
650 }
651 }
652
653 #[test]
655 fn builder_rejects_empty() {
656 match crate::kernel::subcontext::PolydatMatter::builder().build() {
657 Err(msg) => assert!(
658 msg.contains("no input form"),
659 "expected no-form error, got: {msg}"
660 ),
661 Ok(_) => panic!("empty matter must error"),
662 }
663 }
664}