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 for name in Metadata::output_names(self) {
221 let _ = PolydatKernel::pull_ref(self, &name);
222 }
223 }
224 fn pull(&mut self, name: &str) -> Value {
225 PolydatKernel::pull_ref(self, name).clone()
226 }
227 fn input_names(&self) -> Vec<String> {
228 Metadata::input_names(self)
229 }
230 fn output_names(&self) -> Vec<String> {
231 Metadata::output_names(self)
232 }
233 fn output_type(&self, name: &str) -> Option<PortType> {
234 Metadata::output_port_type(self, name)
235 }
236 fn externs(&self) -> Vec<(String, PortType)> {
237 let program = self.program();
238 Metadata::input_names(self)
239 .into_iter()
240 .enumerate()
241 .filter(|(i, _)| program.input_kind(*i) != Some(crate::kernel::InputKind::Coordinate))
242 .filter_map(|(i, name)| Metadata::input_port_type_by_idx(self, i).map(|t| (name, t)))
243 .collect()
244 }
245 fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
246 self.program().cursor_schemas()
247 }
248 fn input_value(&self, name: &str) -> Option<Value> {
249 let idx = self.program().find_input(name)?;
250 Some(self.state_ref().get_input(idx))
251 }
252 fn input_index(&self, name: &str) -> Option<usize> {
253 self.program().find_input(name)
254 }
255 fn set_input_at(
256 &mut self,
257 index: usize,
258 value: Value,
259 ) -> Result<(), crate::kernel::WriteError> {
260 PolydatKernel::set_input_at(self, index, value)
261 }
262 fn output_index(&self, name: &str) -> Option<usize> {
263 self.program().output_index(name)
264 }
265 fn pull_at(&mut self, index: usize) -> Value {
266 PolydatKernel::pull_ref_at(self, index).clone()
267 }
268 fn traversals(&self) -> &[crate::dsl::traversal::Traversal] {
269 self.program().traversals()
270 }
271 fn plan(&self) -> crate::EnginePlan {
272 self.program().engine_plan()
273 }
274 fn traverse(&mut self, index: usize) -> Result<crate::kernel::TraversalStream, String> {
275 PolydatKernel::traverse(self, index)
276 }
277 fn invalidate_all(&mut self) {
278 self.state().invalidate_all();
279 }
280 fn shared_cells(&self) -> Vec<crate::kernel::SharedCellEntry> {
281 self.shared_cells_in_scope()
282 }
283 fn output_cell(&self, name: &str) -> Option<crate::kernel::SharedCell> {
284 self.state_ref().core.output_cell(self.program(), name)
286 }
287 fn output_modifier(&self, name: &str) -> crate::dsl::ast::BindingModifier {
288 self.program().output_modifier(name)
289 }
290 fn cells_in_scope(&self) -> Vec<crate::kernel::SharedCellEntry> {
291 self.shared_cells_in_scope()
294 }
295 fn set_transit_cells(&mut self, cells: Vec<crate::kernel::SharedCellEntry>) {
296 self.replace_transit_cells(cells);
297 }
298 fn scope_coordinates(&self) -> &[crate::kernel::ScopeCoord] {
299 PolydatKernel::scope_coordinates(self)
300 }
301 fn extend_scope_coordinates(&mut self, outer: &[crate::kernel::ScopeCoord]) {
302 PolydatKernel::extend_scope_coordinates(self, outer);
303 }
304 fn input_port_type(&self, name: &str) -> Option<crate::ast::PortType> {
305 self.program().input_port_type(name)
306 }
307 fn bind_input_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> bool {
308 let Some(idx) = self.program().find_input(name) else {
313 return false;
314 };
315 self.state().attach_shared_cell(idx, cell);
316 true
317 }
318 fn attach_shared_cell(
319 &mut self,
320 name: &str,
321 cell: crate::kernel::SharedCell,
322 ) -> Result<(), String> {
323 let program = self.program().clone();
324 let shared = program.shared_outputs();
325 let idx = program.find_input(name).filter(|_| shared.contains(&name));
326 let Some(idx) = idx else {
327 return Err(format!(
328 "no `shared` binding named '{name}'; this kernel's shared bindings are {shared:?}"
329 ));
330 };
331 self.state().attach_shared_cell(idx, cell);
332 Ok(())
333 }
334 fn into_program(self: Box<Self>) -> std::sync::Arc<dyn crate::kernel::KernelProgram> {
335 PolydatKernel::into_program(*self)
336 }
337 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
338 self.program().ledger()
339 }
340 fn coord_count(&self) -> usize {
341 self.program().coord_count()
342 }
343 fn input_value_at(&self, index: usize) -> Option<Value> {
344 (index < self.state_ref().core.inputs.len())
345 .then(|| self.state_ref().read_input_value(index))
346 }
347 fn input_default_at(&self, index: usize) -> Option<Value> {
348 self.program().input_default_by_idx(index).cloned()
349 }
350 fn input_is_cell_bound(&self, index: usize) -> bool {
351 self.state_ref().shared_cell(index).is_some()
352 }
353 fn reset_inputs(&mut self) {
354 let from = self.program().coord_count();
355 self.state().reset_inputs_from(from);
356 }
357 fn fork(&self) -> Box<dyn crate::kernel::Kernel> {
358 Box::new(self.fork_kernel())
359 }
360 fn publish_broadcasts(&mut self) {
361 self.advance_broadcasts();
362 }
363 fn commit_write_throughs(&mut self) -> Result<(), String> {
364 PolydatKernel::commit_write_throughs(self)
365 }
366 fn program_id(&self) -> crate::kernel::ProgramId {
367 crate::kernel::ProgramId(std::sync::Arc::as_ptr(self.program()) as *const () as usize)
368 }
369 fn input_type_origin(&self, name: &str) -> Option<crate::kernel::TypeOrigin> {
370 self.program().input_type_origin(name)
371 }
372}
373
374impl crate::kernel::KernelInternals for PolydatKernel {
375 fn set_write_throughs(&mut self, pairs: Vec<(String, String)>) {
376 PolydatKernel::set_write_throughs(
377 self,
378 pairs
379 .into_iter()
380 .map(
381 |(export_name, source_output)| crate::kernel::KernelWriteThrough {
382 export_name,
383 source_output,
384 },
385 )
386 .collect(),
387 );
388 }
389 fn set_traversals(
390 &mut self,
391 traversals: Vec<crate::dsl::traversal::Traversal>,
392 producers: Vec<crate::dsl::traversal::Producer>,
393 ) {
394 PolydatKernel::set_traversals(self, traversals, producers);
395 }
396 fn folded_value(&self, name: &str) -> Option<Value> {
397 self.get_constant(name).cloned()
398 }
399 fn set_cursor_extent(&mut self, index: usize, extent: u64) {
400 let mut schemas = self.program().cursor_schemas().to_vec();
401 if let Some(schema) = schemas.get_mut(index) {
402 schema.extent = Some(extent);
403 self.set_cursor_schemas(schemas);
404 }
405 }
406}
407
408impl crate::kernel::KernelProgram for crate::kernel::PolydatProgram {
409 fn engine(&self) -> crate::compile::select::Engine {
410 crate::compile::select::Engine::Interpreter(self.cone_mode())
411 }
412 fn as_interpreter(self: std::sync::Arc<Self>) -> Option<std::sync::Arc<Self>> {
413 Some(self)
414 }
415 fn create_kernel(self: std::sync::Arc<Self>) -> Box<dyn crate::kernel::Kernel> {
416 Box::new(PolydatKernel::from_program(self))
417 }
418 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
419 crate::kernel::PolydatProgram::ledger(self)
420 }
421 fn program_id(&self) -> crate::kernel::ProgramId {
422 crate::kernel::ProgramId(self as *const Self as *const () as usize)
423 }
424}
425
426#[cfg(test)]
427#[allow(deprecated)]
429mod tests {
430 use super::*;
431 use crate::dsl::compile::compile_polydat_interpreter;
432
433 #[test]
435 fn dataflow_indexed_set_get() {
436 let mut k = compile_polydat_interpreter("input cycle: u64\nconst x := 7\n").unwrap();
437 k.set_wire(0_usize, Value::U64(42)).expect("typed write");
439 assert_eq!(k.get_wire(0_usize), Some(Value::U64(42)));
440 }
441
442 #[test]
444 fn dataflow_named_set_get() {
445 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
446 k.set_wire("n", Value::U64(5)).expect("typed write");
447 match k.get_wire("n") {
448 Some(Value::U64(5)) => {}
449 other => panic!("expected U64(5), got {other:?}"),
450 }
451 }
452
453 #[test]
455 fn dataflow_string_key() {
456 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
457 let name = String::from("n");
458 k.set_wire(&name, Value::U64(99)).expect("typed write");
459 assert_eq!(k.get_wire(name.clone()), Some(Value::U64(99)));
460 }
461
462 #[test]
464 fn dataflow_unknown_name_safe() {
465 let mut k = compile_polydat_interpreter("input cycle: u64\n").unwrap();
466 let err = k.set_wire("nonexistent", Value::U64(1)).unwrap_err();
467 assert!(matches!(
468 err,
469 crate::kernel::api::WriteError::UnknownWire { .. }
470 ));
471 assert!(k.get_wire("nonexistent").is_none());
472 }
473
474 #[test]
483 fn dataflow_type_mismatch_rejected() {
484 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
485 let err = k
486 .set_wire(
487 "n",
488 Value::VecF32(crate::ast::SliceArc::from_vec(vec![1.0_f32, 2.0])),
489 )
490 .unwrap_err();
491 match err {
492 crate::kernel::api::WriteError::TypeMismatch {
493 slot,
494 expected,
495 got,
496 } => {
497 assert_eq!(slot, "n");
498 assert_eq!(expected, PortType::U64);
499 assert_eq!(got, PortType::VecF32);
500 }
501 other => panic!("expected TypeMismatch, got {other:?}"),
502 }
503 }
504
505 #[test]
510 fn vec_to_scalar_diagnostic_mentions_explicit_helpers() {
511 let err = crate::kernel::api::WriteError::TypeMismatch {
512 slot: "score".into(),
513 expected: PortType::F64,
514 got: PortType::VecF32,
515 };
516 let msg = err.to_string();
517 assert!(
518 msg.contains("reduction node"),
519 "missing reduction hint: {msg}"
520 );
521 assert!(msg.contains("vec_dot"), "missing vec_dot hint: {msg}");
522 }
523
524 #[test]
527 fn dataflow_healable_mismatch_adapts() {
528 let mut k = compile_polydat_interpreter("input cycle: u64\nextern x: f64\n").unwrap();
529 k.set_wire("x", Value::U64(42))
532 .expect("u64→f64 boundary adapter");
533 match k.get_wire("x") {
534 Some(Value::F64(42.0)) => {}
535 other => panic!("expected adapted F64(42.0), got {other:?}"),
536 }
537 }
538
539 #[test]
543 fn dataflow_none_passes_through_any_slot() {
544 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
545 k.set_wire("n", Value::None).expect("None always permitted");
546 }
547
548 #[test]
550 fn metadata_listings() {
551 let k = compile_polydat_interpreter(
552 "input (cycle: u64, thread: u64)\nextern n: u64\nconst x := 7\n",
553 )
554 .unwrap();
555 let inputs: Vec<String> = k.input_names();
556 assert!(inputs.iter().any(|s| s == "cycle"));
557 assert!(inputs.iter().any(|s| s == "n"));
558 assert_eq!(k.coord_count(), 2); assert!(k.find_input("n").is_some());
560 assert_eq!(k.input_port_type("n"), Some(PortType::U64));
561 }
562
563 #[test]
567 fn construction_symmetric_paths() {
568 let root_opts = crate::kernel::subcontext::CompileOptions {
569 workload_dir: None,
570 polydat_lib_paths: Vec::new(),
571 strict: false,
572 required_outputs: Vec::new(),
573 context_label: Some("root".to_string()),
574 cursor_limit: None,
575 ..Default::default()
576 };
577 let root_matter = crate::kernel::subcontext::PolydatMatter::builder()
578 .label("root")
579 .source("input cycle: u64\nshared flag := 0\n")
580 .options(root_opts)
581 .build()
582 .expect("matter build");
583 let root =
584 <PolydatKernel as Construction>::root(root_matter).expect("root from source matter");
585
586 let sub_opts = crate::kernel::subcontext::CompileOptions {
587 workload_dir: None,
588 polydat_lib_paths: Vec::new(),
589 strict: false,
590 required_outputs: Vec::new(),
591 context_label: Some("sub".to_string()),
592 cursor_limit: None,
593 ..Default::default()
594 };
595 let sub_matter = crate::kernel::subcontext::PolydatMatter::builder()
596 .label("sub")
597 .source("input cycle: u64\n")
598 .options(sub_opts)
599 .build()
600 .expect("matter build");
601 let _sub = root
602 .subscope(sub_matter)
603 .expect("subscope from source matter");
604 }
605
606 #[test]
610 fn construction_root_from_program() {
611 let template = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
612 let program = template.program().clone();
613 let matter = crate::kernel::subcontext::PolydatMatter::builder()
614 .program(program)
615 .build()
616 .expect("matter build");
617 let mut root =
618 <PolydatKernel as Construction>::root(matter).expect("root from program matter");
619 root.set_wire("n", Value::U64(13)).expect("set_wire");
620 assert_eq!(root.get_wire("n"), Some(Value::U64(13)));
621 }
622
623 #[test]
625 fn builder_rejects_multiple_forms() {
626 let template = compile_polydat_interpreter("input cycle: u64\n").unwrap();
627 match crate::kernel::subcontext::PolydatMatter::builder()
628 .source("input cycle: u64\n")
629 .program(template.program().clone())
630 .build()
631 {
632 Err(msg) => assert!(
633 msg.contains("multiple"),
634 "expected multiple-forms error, got: {msg}"
635 ),
636 Ok(_) => panic!("multiple forms must error"),
637 }
638 }
639
640 #[test]
642 fn builder_rejects_empty() {
643 match crate::kernel::subcontext::PolydatMatter::builder().build() {
644 Err(msg) => assert!(
645 msg.contains("no input form"),
646 "expected no-form error, got: {msg}"
647 ),
648 Ok(_) => panic!("empty matter must error"),
649 }
650 }
651}