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 #[inline]
55 fn get_wire_idx(&self, idx: usize) -> Value {
56 self.state_ref().get_input(idx)
57 }
58}
59
60impl Construction for PolydatKernel {
61 type Error = crate::kernel::subcontext::ContractViolation;
62
63 fn root(matter: crate::kernel::subcontext::PolydatMatter<'_>) -> Result<Self, Self::Error> {
64 use crate::kernel::subcontext::PolydatMatterInner;
65 match matter.inner {
66 PolydatMatterInner::Source(s) => {
67 let options = crate::dsl::compile::CompileOptions {
68 source_dir: s.options.workload_dir.clone(),
69 lib_paths: s.options.polydat_lib_paths,
70 required_outputs: s.options.required_outputs.clone(),
71 strict: s.options.strict,
72 context: s
73 .options
74 .context_label
75 .clone()
76 .unwrap_or_else(|| s.label.clone()),
77 cursor_limit: s.options.cursor_limit,
78 input_variance: s.options.input_variance,
79 inferred_externs: Vec::new(),
80 ledger: None,
81 resources: None,
82 engine: crate::Engine::default(),
83 };
84 crate::dsl::compile::compile_polydat_interpreter_with_options(
85 &s.body, &options, None,
86 )
87 .map_err(|e| crate::kernel::subcontext::ContractViolation::Compile(e.to_string()))
88 }
89 PolydatMatterInner::Statements(s) => {
90 let file = crate::dsl::ast::PolydatFile {
95 statements: s.statements,
96 };
97 let options = crate::dsl::compile::CompileOptions {
98 source_dir: s.options.workload_dir.clone(),
99 lib_paths: s.options.polydat_lib_paths,
100 required_outputs: s.options.required_outputs.clone(),
101 strict: s.options.strict,
102 context: s
103 .options
104 .context_label
105 .clone()
106 .unwrap_or_else(|| s.label.clone()),
107 cursor_limit: None,
108 input_variance: s.options.input_variance,
109 inferred_externs: Vec::new(),
110 ledger: None,
111 resources: None,
112 engine: crate::Engine::default(),
113 };
114 crate::dsl::compile::compile_ast_interpreter_with_options(&file, "", &options, None)
115 .map_err(|e| {
116 crate::kernel::subcontext::ContractViolation::Compile(e.to_string())
117 })
118 }
119 PolydatMatterInner::Program(p) => {
120 let engine = p.program.engine();
121 let program = p.program.as_interpreter().ok_or_else(|| {
122 crate::kernel::subcontext::ContractViolation::Compile(format!(
123 "a root interpreter kernel needs an interpreter program; this one is \
124 on the {engine} engine, whose `create_kernel` makes its root"
125 ))
126 })?;
127 let mut k = PolydatKernel::from_program(program);
128 for (var, value) in p.iter_bindings {
129 if let Some(idx) = k.program().find_input(var) {
130 k.state().set_input(idx, value.clone());
131 }
132 }
133 Ok(k)
134 }
135 }
136 }
137
138 fn subscope(
139 &self,
140 matter: crate::kernel::subcontext::PolydatMatter<'_>,
141 ) -> Result<Box<dyn crate::kernel::Kernel>, Self::Error> {
142 matter.build_under(self)
143 }
144}
145
146impl crate::kernel::Kernel for PolydatKernel {
149 fn engine(&self) -> crate::compile::select::Engine {
150 crate::compile::select::Engine::Interpreter(self.program().cone_mode())
151 }
152 fn set_inputs(&mut self, coords: &[u64]) {
153 PolydatKernel::set_inputs(self, coords);
154 }
155 fn set_input(&mut self, name: &str, value: Value) -> Result<(), crate::kernel::WriteError> {
156 PolydatKernel::set_input(self, name, value)
157 }
158 fn set_cursor(
159 &mut self,
160 name: &str,
161 partition: &crate::iteration::cursor_partition::Partition,
162 ) -> Result<(), crate::kernel::WriteError> {
163 PolydatKernel::set_cursor(self, name, partition)
164 }
165 fn eval(&mut self) {
168 PolydatKernel::eval_read(self);
169 }
170 fn pull(&mut self, name: &str) -> Value {
171 PolydatKernel::pull_ref(self, name).clone()
172 }
173 fn input_names(&self) -> Vec<String> {
174 Metadata::input_names(self)
175 }
176 fn output_names(&self) -> Vec<String> {
177 Metadata::output_names(self)
178 }
179 fn output_type(&self, name: &str) -> Option<PortType> {
180 Metadata::output_port_type(self, name)
181 }
182 fn externs(&self) -> Vec<(String, PortType)> {
183 let program = self.program();
184 Metadata::input_names(self)
185 .into_iter()
186 .enumerate()
187 .filter(|(i, _)| program.input_kind(*i) != Some(crate::kernel::InputKind::Coordinate))
188 .filter_map(|(i, name)| Metadata::input_port_type_by_idx(self, i).map(|t| (name, t)))
189 .collect()
190 }
191 fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
192 self.program().cursor_schemas()
193 }
194 fn input_value(&self, name: &str) -> Option<Value> {
195 let idx = self.program().find_input(name)?;
196 Some(self.state_ref().get_input(idx))
197 }
198 fn input_index(&self, name: &str) -> Option<usize> {
199 self.program().find_input(name)
200 }
201 fn set_input_at(
202 &mut self,
203 index: usize,
204 value: Value,
205 ) -> Result<(), crate::kernel::WriteError> {
206 PolydatKernel::set_input_at(self, index, value)
207 }
208 fn output_index(&self, name: &str) -> Option<usize> {
209 self.program().output_index(name)
210 }
211 fn const_inits(&self) -> &[crate::kernel::ConstInit] {
212 self.program().const_inits()
213 }
214 fn init_input_at(
215 &mut self,
216 index: usize,
217 value: Value,
218 ) -> Result<(), crate::kernel::WriteError> {
219 PolydatKernel::init_input_at(self, index, value)
220 }
221 fn pull_at(&mut self, index: usize) -> Value {
222 PolydatKernel::pull_ref_at(self, index).clone()
223 }
224 fn traversals(&self) -> &[crate::dsl::traversal::Traversal] {
225 self.program().traversals()
226 }
227 fn plan(&self) -> crate::EnginePlan {
228 self.program().engine_plan()
229 }
230 fn traverse(&mut self, index: usize) -> Result<crate::kernel::TraversalStream, String> {
231 PolydatKernel::traverse(self, index)
232 }
233 fn invalidate_all(&mut self) {
234 self.state().invalidate_all();
235 }
236 fn shared_cells(&self) -> Vec<crate::kernel::SharedCellEntry> {
237 self.shared_cells_in_scope()
238 }
239 fn output_cell(&self, name: &str) -> Option<crate::kernel::SharedCell> {
240 self.state_ref().core.output_cell(self.program(), name)
242 }
243 fn output_modifier(&self, name: &str) -> crate::dsl::ast::BindingModifier {
244 self.program().output_modifier(name)
245 }
246 fn cells_in_scope(&self) -> Vec<crate::kernel::SharedCellEntry> {
247 self.shared_cells_in_scope()
250 }
251 fn set_transit_cells(&mut self, cells: Vec<crate::kernel::SharedCellEntry>) {
252 self.replace_transit_cells(cells);
253 }
254 fn scope_coordinates(&self) -> &[crate::kernel::ScopeCoord] {
255 PolydatKernel::scope_coordinates(self)
256 }
257 fn extend_scope_coordinates(&mut self, outer: &[crate::kernel::ScopeCoord]) {
258 PolydatKernel::extend_scope_coordinates(self, outer);
259 }
260 fn input_port_type(&self, name: &str) -> Option<crate::ast::PortType> {
261 self.program().input_port_type(name)
262 }
263 fn bind_input_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> bool {
264 let Some(idx) = self.program().find_input(name) else {
269 return false;
270 };
271 self.state().attach_shared_cell(idx, cell);
272 true
273 }
274 fn attach_shared_cell(
275 &mut self,
276 name: &str,
277 cell: crate::kernel::SharedCell,
278 ) -> Result<(), String> {
279 let program = self.program().clone();
280 let shared = program.shared_outputs();
281 let idx = program.find_input(name).filter(|_| shared.contains(&name));
282 let Some(idx) = idx else {
283 return Err(format!(
284 "no `shared` binding named '{name}'; this kernel's shared bindings are {shared:?}"
285 ));
286 };
287 self.state().attach_shared_cell(idx, cell);
288 Ok(())
289 }
290 fn into_program(self: Box<Self>) -> std::sync::Arc<dyn crate::kernel::KernelProgram> {
291 PolydatKernel::into_program(*self)
292 }
293 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
294 self.program().ledger()
295 }
296 fn resources(&self) -> &crate::resource::ResourceScope {
297 self.program().resources()
298 }
299 fn canonical_hash(&self) -> [u8; 32] {
300 self.program().canonical_hash()
301 }
302 fn coord_count(&self) -> usize {
303 self.program().coord_count()
304 }
305 fn input_value_at(&self, index: usize) -> Option<Value> {
306 (index < self.state_ref().core.inputs.len())
307 .then(|| self.state_ref().read_input_value(index))
308 }
309 fn input_default_at(&self, index: usize) -> Option<Value> {
310 self.program().input_default_by_idx(index).cloned()
311 }
312 fn input_is_cell_bound(&self, index: usize) -> bool {
313 self.state_ref().shared_cell(index).is_some()
314 }
315 fn reset_inputs(&mut self) {
316 let from = self.program().coord_count();
317 self.state().reset_inputs_from(from);
318 }
319 fn fork(&self) -> Box<dyn crate::kernel::Kernel> {
320 Box::new(self.fork_kernel())
321 }
322 fn publish_broadcasts(&mut self) {
323 self.advance_broadcasts();
324 }
325 fn commit_write_throughs(&mut self) -> Result<(), String> {
326 PolydatKernel::commit_write_throughs(self)
327 }
328 fn program_id(&self) -> crate::kernel::ProgramId {
329 crate::kernel::ProgramId(std::sync::Arc::as_ptr(self.program()) as *const () as usize)
330 }
331 fn input_type_origin(&self, name: &str) -> Option<crate::kernel::TypeOrigin> {
332 self.program().input_type_origin(name)
333 }
334 fn as_interpreter(&self) -> Option<&PolydatKernel> {
335 Some(self)
336 }
337 fn as_interpreter_mut(&mut self) -> Option<&mut PolydatKernel> {
338 Some(self)
339 }
340}
341
342impl crate::kernel::KernelInternals for PolydatKernel {
343 fn set_inherited_outputs(&mut self, names: Vec<String>) {
344 PolydatKernel::mark_inherited_outputs(self, names);
345 }
346 fn set_write_throughs(&mut self, pairs: Vec<(String, String)>) {
347 PolydatKernel::set_write_throughs(
348 self,
349 pairs
350 .into_iter()
351 .map(
352 |(export_name, source_output)| crate::kernel::KernelWriteThrough {
353 export_name,
354 source_output,
355 },
356 )
357 .collect(),
358 );
359 }
360 fn set_traversals(
361 &mut self,
362 traversals: Vec<crate::dsl::traversal::Traversal>,
363 producers: Vec<crate::dsl::traversal::Producer>,
364 ) {
365 PolydatKernel::set_traversals(self, traversals, producers);
366 }
367 fn folded_value(&self, name: &str) -> Option<Value> {
370 if !self.program().is_fixed_output(name) {
371 return None;
372 }
373 self.get_constant(name).cloned()
374 }
375 fn set_cursor_extent(&mut self, index: usize, extent: u64) {
376 let mut schemas = self.program().cursor_schemas().to_vec();
377 if let Some(schema) = schemas.get_mut(index) {
378 schema.extent = Some(extent);
379 self.set_cursor_schemas(schemas);
380 }
381 }
382}
383
384impl crate::kernel::KernelProgram for crate::kernel::PolydatProgram {
385 fn engine(&self) -> crate::compile::select::Engine {
386 crate::compile::select::Engine::Interpreter(self.cone_mode())
387 }
388 fn as_interpreter(self: std::sync::Arc<Self>) -> Option<std::sync::Arc<Self>> {
389 Some(self)
390 }
391 fn create_uninitialized(self: std::sync::Arc<Self>) -> Box<dyn crate::kernel::Kernel> {
392 Box::new(PolydatKernel::from_program(self))
393 }
394 fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
395 crate::kernel::PolydatProgram::ledger(self)
396 }
397 fn resources(&self) -> &crate::resource::ResourceScope {
398 crate::kernel::PolydatProgram::resources(self)
399 }
400 fn canonical_hash(&self) -> [u8; 32] {
401 crate::kernel::PolydatProgram::canonical_hash(self)
402 }
403 fn program_id(&self) -> crate::kernel::ProgramId {
404 crate::kernel::ProgramId(self as *const Self as *const () as usize)
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use crate::dsl::compile::compile_polydat_interpreter;
412
413 #[test]
415 fn dataflow_indexed_get() {
416 let mut k = compile_polydat_interpreter("input cycle: u64\nconst x := 7\n").unwrap();
417 k.set_inputs(&[42]);
419 assert_eq!(k.get_wire(0_usize), Some(Value::U64(42)));
420 }
421
422 #[test]
424 fn dataflow_named_set_get() {
425 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
426 k.set_input("n", Value::U64(5)).expect("typed write");
427 match k.get_wire("n") {
428 Some(Value::U64(5)) => {}
429 other => panic!("expected U64(5), got {other:?}"),
430 }
431 }
432
433 #[test]
435 fn dataflow_string_key() {
436 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
437 let name = String::from("n");
438 k.set_input(&name, Value::U64(99)).expect("typed write");
439 assert_eq!(k.get_wire(&name), Some(Value::U64(99)));
440 assert_eq!(k.get_wire(name.clone()), Some(Value::U64(99)));
441 }
442
443 #[test]
445 fn dataflow_unknown_name_safe() {
446 let mut k = compile_polydat_interpreter("input cycle: u64\n").unwrap();
447 let err = k.set_input("nonexistent", Value::U64(1)).unwrap_err();
448 assert!(matches!(
449 err,
450 crate::kernel::api::WriteError::UnknownWire { .. }
451 ));
452 assert!(k.get_wire("nonexistent").is_none());
453 }
454
455 #[test]
458 fn dataflow_type_mismatch_rejected() {
459 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
460 let err = k
461 .set_input(
462 "n",
463 Value::VecF32(crate::ast::SliceArc::from_vec(vec![1.0_f32, 2.0])),
464 )
465 .unwrap_err();
466 match err {
467 crate::kernel::api::WriteError::TypeMismatch {
468 slot,
469 expected,
470 got,
471 } => {
472 assert_eq!(slot, "n");
473 assert_eq!(expected, PortType::U64);
474 assert_eq!(got, PortType::VecF32);
475 }
476 other => panic!("expected TypeMismatch, got {other:?}"),
477 }
478 }
479
480 #[test]
485 fn vec_to_scalar_diagnostic_mentions_explicit_helpers() {
486 let err = crate::kernel::api::WriteError::TypeMismatch {
487 slot: "score".into(),
488 expected: PortType::F64,
489 got: PortType::VecF32,
490 };
491 let msg = err.to_string();
492 assert!(
493 msg.contains("reduction node"),
494 "missing reduction hint: {msg}"
495 );
496 assert!(msg.contains("vec_dot"), "missing vec_dot hint: {msg}");
497 }
498
499 #[test]
502 fn typed_write_converts_through_to_port() {
503 let mut k = compile_polydat_interpreter("input cycle: u64\nextern x: f64\n").unwrap();
504 assert!(matches!(
505 k.set_input("x", Value::U64(42)),
506 Err(crate::kernel::api::WriteError::TypeMismatch { .. })
507 ));
508 let converted =
509 crate::convert::to_port(Value::U64(42), PortType::F64).expect("u64 converts to f64");
510 k.set_input("x", converted).expect("converted value");
511 match k.get_wire("x") {
512 Some(Value::F64(42.0)) => {}
513 other => panic!("expected converted F64(42.0), got {other:?}"),
514 }
515 }
516
517 #[test]
521 fn dataflow_none_passes_through_any_slot() {
522 let mut k = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
523 k.set_input("n", Value::None)
524 .expect("None always permitted");
525 }
526
527 #[test]
529 fn metadata_listings() {
530 let k = compile_polydat_interpreter(
531 "input (cycle: u64, thread: u64)\nextern n: u64\nconst x := 7\n",
532 )
533 .unwrap();
534 let inputs: Vec<String> = k.input_names();
535 assert!(inputs.iter().any(|s| s == "cycle"));
536 assert!(inputs.iter().any(|s| s == "n"));
537 assert_eq!(k.coord_count(), 2); assert!(k.find_input("n").is_some());
539 assert_eq!(k.input_port_type("n"), Some(PortType::U64));
540 }
541
542 #[test]
546 fn construction_symmetric_paths() {
547 let root_opts = crate::kernel::subcontext::CompileOptions {
548 workload_dir: None,
549 polydat_lib_paths: Vec::new(),
550 strict: false,
551 required_outputs: Vec::new(),
552 context_label: Some("root".to_string()),
553 cursor_limit: None,
554 ..Default::default()
555 };
556 let root_matter = crate::kernel::subcontext::PolydatMatter::builder()
557 .label("root")
558 .source("input cycle: u64\nshared flag := 0\n")
559 .options(root_opts)
560 .build()
561 .expect("matter build");
562 let root =
563 <PolydatKernel as Construction>::root(root_matter).expect("root from source matter");
564
565 let sub_opts = crate::kernel::subcontext::CompileOptions {
566 workload_dir: None,
567 polydat_lib_paths: Vec::new(),
568 strict: false,
569 required_outputs: Vec::new(),
570 context_label: Some("sub".to_string()),
571 cursor_limit: None,
572 ..Default::default()
573 };
574 let sub_matter = crate::kernel::subcontext::PolydatMatter::builder()
575 .label("sub")
576 .source("input cycle: u64\n")
577 .options(sub_opts)
578 .build()
579 .expect("matter build");
580 let _sub = root
581 .subscope(sub_matter)
582 .expect("subscope from source matter");
583 }
584
585 #[test]
589 fn construction_root_from_program() {
590 let template = compile_polydat_interpreter("input cycle: u64\nextern n: u64\n").unwrap();
591 let program = template.program().clone();
592 let matter = crate::kernel::subcontext::PolydatMatter::builder()
593 .program(program)
594 .build()
595 .expect("matter build");
596 let mut root =
597 <PolydatKernel as Construction>::root(matter).expect("root from program matter");
598 root.set_input("n", Value::U64(13)).expect("set_input");
599 assert_eq!(root.get_wire("n"), Some(Value::U64(13)));
600 }
601
602 #[test]
604 fn builder_rejects_multiple_forms() {
605 let template = compile_polydat_interpreter("input cycle: u64\n").unwrap();
606 match crate::kernel::subcontext::PolydatMatter::builder()
607 .source("input cycle: u64\n")
608 .program(template.program().clone())
609 .build()
610 {
611 Err(msg) => assert!(
612 msg.contains("multiple"),
613 "expected multiple-forms error, got: {msg}"
614 ),
615 Ok(_) => panic!("multiple forms must error"),
616 }
617 }
618
619 #[test]
621 fn builder_rejects_empty() {
622 match crate::kernel::subcontext::PolydatMatter::builder().build() {
623 Err(msg) => assert!(
624 msg.contains("no input form"),
625 "expected no-form error, got: {msg}"
626 ),
627 Ok(_) => panic!("empty matter must error"),
628 }
629 }
630}