1use std::collections::HashMap;
16
17use crate::ast::SlotShape;
18use crate::ast::{PolydatNode, PortType};
19use crate::compile::closures::{
20 CompiledKernelPull, CompiledKernelPush, CompiledKernelPushPull, CompiledKernelRaw,
21};
22use crate::compile::select::{self, ProvMode};
23use crate::kernel::{PolydatKernel, PolydatProgram, WireSource};
24use crate::library::convert::{F64ToString, U64ToF64, U64ToString};
25use crate::library::json::JsonToStr;
26
27#[derive(Debug, Clone)]
30pub enum WireRef {
31 Input(String),
33 Node(String, usize),
35}
36
37impl WireRef {
38 pub fn node(name: impl Into<String>) -> Self {
40 WireRef::Node(name.into(), 0)
41 }
42
43 pub fn node_port(name: impl Into<String>, port: usize) -> Self {
45 WireRef::Node(name.into(), port)
46 }
47
48 pub fn input(name: impl Into<String>) -> Self {
50 WireRef::Input(name.into())
51 }
52}
53
54struct PendingNode {
55 name: String,
56 node: Box<dyn PolydatNode>,
57 inputs: Vec<WireRef>,
58 strict_values: Option<bool>,
64}
65
66#[derive(Debug)]
68pub enum AssemblyError {
69 UnknownWire(String),
71 TypeMismatch {
73 from_node: String,
75 from_port: usize,
77 from_type: PortType,
79 to_node: String,
81 to_port: usize,
83 to_type: PortType,
85 },
86 DuplicateNode(String),
88 CycleDetected,
90 ArityMismatch {
92 node_name: String,
94 expected: usize,
96 got: usize,
98 },
99 ConstantFold(String),
105 ConstInit {
109 name: String,
111 reason: String,
113 },
114 OpenInputs(Vec<(String, PortType)>),
118 NativeCone {
122 cone: String,
124 reason: String,
126 },
127 Other(String),
129}
130
131impl std::fmt::Display for AssemblyError {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 match self {
134 AssemblyError::UnknownWire(name) => {
135 write!(f, "unknown wire: '{name}'\n\n")?;
136 writeln!(f, " No node output or coordinate named '{name}' exists.")?;
137 write!(
138 f,
139 " Check spelling, or add a node that produces this output."
140 )
141 }
142 AssemblyError::TypeMismatch {
143 from_node,
144 from_port,
145 from_type,
146 to_node,
147 to_port,
148 to_type,
149 } => {
150 writeln!(
151 f,
152 "type mismatch: cannot connect {from_type} output to {to_type} input"
153 )?;
154 writeln!(f)?;
155 writeln!(
156 f,
157 " {from_node} [{from_port}] ──({from_type})──▶ {to_node} [{to_port}] expects {to_type}"
158 )?;
159 writeln!(f)?;
160 let suggestion = match (from_type, to_type) {
162 (PortType::U64, PortType::Str) => {
163 Some("This should auto-convert. If you see this, file a bug.")
164 }
165 (PortType::F64, PortType::Str) => {
166 Some("This should auto-convert. If you see this, file a bug.")
167 }
168 (PortType::U64, PortType::F64) => {
169 Some("This should auto-convert. If you see this, file a bug.")
170 }
171 (PortType::U64, PortType::Bytes) => {
172 Some("Add u64_to_bytes() between them to convert.")
173 }
174 (PortType::Str, PortType::Bytes) => {
175 Some("String cannot be directly used as bytes.")
176 }
177 (PortType::U64, PortType::Json) => {
178 Some("Add to_json() between them to wrap as JSON.")
179 }
180 (PortType::Str, PortType::Json) => {
181 Some("Add str_to_json() to parse the string as JSON.")
182 }
183 (PortType::Bytes, PortType::Str) => {
184 Some("Add to_hex() or to_base64() to convert bytes to string.")
185 }
186 (PortType::Bytes, PortType::U64) => {
187 Some("Bytes cannot be directly converted to u64.")
188 }
189 _ => None,
190 };
191 if let Some(hint) = suggestion {
192 write!(f, " Hint: {hint}")?;
193 }
194 Ok(())
195 }
196 AssemblyError::DuplicateNode(name) => {
197 write!(f, "duplicate node name: '{name}'\n\n")?;
198 write!(f, " Two nodes cannot share the same name.")
199 }
200 AssemblyError::CycleDetected => {
201 write!(f, "cycle detected in DAG\n\n")?;
202 writeln!(
203 f,
204 " The graph contains a loop. Polydat graphs must be acyclic"
205 )?;
206 write!(f, " (data flows in one direction only).")
207 }
208 AssemblyError::ArityMismatch {
209 node_name,
210 expected,
211 got,
212 } => {
213 write!(f, "wrong number of inputs for '{node_name}'\n\n")?;
214 writeln!(f, " Expected {expected} input(s), but got {got}.")?;
215 if *got < *expected {
216 write!(f, " Connect more wires to this node's input ports.")
217 } else {
218 write!(f, " Disconnect extra wires from this node.")
219 }
220 }
221 AssemblyError::ConstantFold(msg) => write!(
222 f,
223 "a value this program computes at build could not be computed: {msg}"
224 ),
225 AssemblyError::ConstInit { name, reason } => write!(
226 f,
227 "the const '{name}' could not be computed when the kernel was initialized: {reason}"
228 ),
229 AssemblyError::OpenInputs(inputs) => {
230 writeln!(
231 f,
232 "these inputs' types were inferred, not declared, and input variance \
233 is set to refuse them:"
234 )?;
235 for (name, ty) in inputs {
236 writeln!(f, " {name} (inferred {ty})")?;
237 }
238 write!(
239 f,
240 "Declare each one's type (`extern name: <type>`), or set \
241 `input_variance` to `Warn` or `Info` to convert what is written to them."
242 )
243 }
244 AssemblyError::NativeCone { cone, reason } => write!(
245 f,
246 "native code generation failed for cone {cone} under JitMode::Force: {reason}"
247 ),
248 AssemblyError::Other(msg) => write!(f, "{msg}"),
249 }
250 }
251}
252
253impl std::error::Error for AssemblyError {}
254
255pub(crate) struct ResolvedDag {
257 pub(crate) nodes: Vec<Box<dyn PolydatNode>>,
259 pub(crate) wiring: Vec<Vec<WireSource>>,
261 pub(crate) input_defs: Vec<crate::kernel::InputDef>,
263 pub(crate) coord_count: usize,
265 pub(crate) output_map: HashMap<String, (usize, usize)>,
267 pub(crate) output_order: Vec<String>,
269 pub(crate) source: String,
271 pub(crate) context: String,
273 pub(crate) output_modifiers: HashMap<String, crate::dsl::ast::BindingModifier>,
275 pub(crate) const_outputs: std::collections::HashSet<String>,
278 pub(crate) const_inits: Vec<crate::kernel::ConstInit>,
280 pub(crate) cursor_schemas: Vec<crate::iteration::source::SourceSchema>,
282 pub(crate) ledger: std::sync::Arc<crate::kernel::CompileLedger>,
284 pub(crate) resources: crate::resource::ResourceScope,
287}
288
289impl ResolvedDag {
290 fn input_names(&self) -> Vec<String> {
292 self.input_defs[..self.coord_count]
293 .iter()
294 .map(|d| d.name.clone())
295 .collect()
296 }
297
298 pub(crate) fn graph_identity(&self) -> [u8; 32] {
302 crate::kernel::IdentityGraph {
303 nodes: &self.nodes,
304 wiring: &self.wiring,
305 input_defs: &self.input_defs,
306 outputs: self
307 .output_map
308 .iter()
309 .map(|(n, &(ni, pi))| (n.as_str(), ni, pi))
310 .collect(),
311 output_modifiers: &self.output_modifiers,
312 const_outputs: &self.const_outputs,
313 }
314 .digest()
315 }
316}
317
318struct SlotLayout {
323 input_starts: Vec<usize>,
325 coord_slots: usize,
327 port_offsets: Vec<Vec<usize>>,
329 total_slots: usize,
331}
332
333fn slot_layout(resolved: &ResolvedDag) -> SlotLayout {
334 let mut input_starts = Vec::with_capacity(resolved.coord_count);
335 let mut next = 0usize;
336 for d in &resolved.input_defs {
337 input_starts.push(next);
338 next += d.port_type.slot_width();
339 }
340 let coord_slots = next;
341 let mut port_offsets: Vec<Vec<usize>> = Vec::with_capacity(resolved.nodes.len());
342 for node in &resolved.nodes {
343 let mut po = Vec::with_capacity(node.meta().outs.len());
344 for out in &node.meta().outs {
345 po.push(next);
346 next += out.typ.slot_width();
347 }
348 port_offsets.push(po);
349 }
350 SlotLayout {
351 input_starts,
352 coord_slots,
353 port_offsets,
354 total_slots: next,
355 }
356}
357
358fn node_step_op(
364 node: &dyn crate::ast::PolydatNode,
365 wire_types: &[PortType],
366) -> Option<(
367 crate::compile::closures::StepOp,
368 Vec<crate::ast::ScratchElem>,
369)> {
370 let meta = node.meta();
374 if (meta.name == "identity" || meta.name.starts_with("__port_")) && meta.outs.len() == 1 {
375 return Some(match meta.outs[0].typ.slot_color() {
376 crate::ast::SlotColor::Ref2 => {
377 let kit = ref_copy_kit(meta.outs[0].typ)?;
378 (crate::compile::closures::StepOp::Slot(kit.op), kit.scratch)
379 }
380 _ => (crate::compile::closures::StepOp::Copy, Vec::new()),
381 });
382 }
383 if let Some(op) = node.compiled_u64() {
384 return Some((crate::compile::closures::StepOp::U64(op), Vec::new()));
385 }
386 node.compiled_slot(
387 wire_types,
388 crate::compile::select::Engine::Closures(crate::compile::select::Provenance::Auto),
389 )
390 .map(|kit| (crate::compile::closures::StepOp::Slot(kit.op), kit.scratch))
391}
392
393pub(crate) fn scratch_pairs(
404 name: &str,
405 ref_starts: &[usize],
406 scratch: &[crate::ast::ScratchElem],
407 base: usize,
408) -> Vec<(usize, usize)> {
409 use crate::ast::ScratchElem;
410 let publishing: Vec<usize> = scratch
411 .iter()
412 .enumerate()
413 .filter(|(_, e)| {
414 !matches!(
415 e,
416 ScratchElem::Slots | ScratchElem::Kernels | ScratchElem::State
417 )
418 })
419 .map(|(k, _)| base + k)
420 .collect();
421 assert!(
422 publishing.len() <= ref_starts.len(),
423 "slot-op step '{name}' declares {} publishing scratch entries for {} Ref output ports",
424 publishing.len(),
425 ref_starts.len()
426 );
427 ref_starts.iter().copied().zip(publishing).collect()
428}
429
430pub(crate) fn ref_copy_kit(ty: PortType) -> Option<crate::ast::CompiledSlotKit> {
436 use crate::ast::ScratchBuf;
437 let elem = ty.scratch_elem()?;
438 Some(crate::ast::CompiledSlotKit {
439 scratch: vec![elem],
440 op: Box::new(
441 move |inputs: &[u64], outputs: &mut [u64], scratch: &mut [ScratchBuf]| {
442 let (p, n) = (inputs[0] as usize, inputs[1] as usize);
443 macro_rules! copy_into {
444 ($v:expr, $t:ty) => {{
445 $v.clear();
446 $v.extend_from_slice(unsafe {
450 std::slice::from_raw_parts(p as *const $t, n)
451 });
452 }};
453 }
454 match &mut scratch[0] {
455 ScratchBuf::Str(v) | ScratchBuf::Bytes(v) => copy_into!(v, u8),
456 ScratchBuf::F32(v) => copy_into!(v, f32),
457 ScratchBuf::F64(v) => copy_into!(v, f64),
458 ScratchBuf::F16(v) => copy_into!(v, half::f16),
459 ScratchBuf::I8(v) => copy_into!(v, i8),
460 ScratchBuf::I16(v) => copy_into!(v, i16),
461 ScratchBuf::I32(v) => copy_into!(v, i32),
462 ScratchBuf::I64(v) => copy_into!(v, i64),
463 ScratchBuf::Value(v) => {
464 v.clear();
465 if n > 0 {
466 v.push(unsafe { (*(p as *const crate::ast::Value)).clone() });
468 }
469 }
470 ScratchBuf::Slots(_) | ScratchBuf::Kernels(_) | ScratchBuf::State(_) => {
471 unreachable!("a copy owns only a value entry")
472 }
473 }
474 let (ptr, len) = scratch[0].ptr_len();
475 outputs[0] = ptr;
476 outputs[1] = len;
477 },
478 ),
479 })
480}
481
482pub(crate) fn identity_op(node: &dyn crate::ast::PolydatNode) -> Option<crate::ast::CompiledU64Op> {
488 let meta = node.meta();
489 if meta.name != "identity" || meta.outs.len() != 1 {
490 return None;
491 }
492 if meta.outs[0].typ.slot_color() == crate::ast::SlotColor::Ref2 {
493 return None;
494 }
495 Some(Box::new(|inputs: &[u64], outputs: &mut [u64]| {
496 outputs.copy_from_slice(inputs)
497 }))
498}
499
500impl SlotLayout {
501 fn input_slots(&self, resolved: &ResolvedDag, node_idx: usize) -> Vec<usize> {
504 let mut slots = Vec::new();
505 for source in &resolved.wiring[node_idx] {
506 let (start, w) = match source {
507 WireSource::Input(c) => (
508 self.input_starts.get(*c).copied().unwrap_or(*c),
509 resolved
510 .input_defs
511 .get(*c)
512 .map(|d| d.port_type.slot_width())
513 .unwrap_or(1),
514 ),
515 WireSource::NodeOutput(u, p) => (
516 self.port_offsets[*u][*p],
517 resolved.nodes[*u].meta().outs[*p].typ.slot_width(),
518 ),
519 };
520 slots.extend(start..start + w);
521 }
522 slots
523 }
524
525 fn output_slots(&self, resolved: &ResolvedDag, node_idx: usize) -> Vec<usize> {
527 let mut slots = Vec::new();
528 for (p, out) in resolved.nodes[node_idx].meta().outs.iter().enumerate() {
529 let start = self.port_offsets[node_idx][p];
530 slots.extend(start..start + out.typ.slot_width());
531 }
532 slots
533 }
534
535 fn named_outputs(&self, resolved: &ResolvedDag) -> HashMap<String, usize> {
537 resolved
538 .output_map
539 .iter()
540 .map(|(name, (n, p))| (name.clone(), self.port_offsets[*n][*p]))
541 .collect()
542 }
543
544 fn ref_slot_mask(&self, resolved: &ResolvedDag) -> Vec<bool> {
550 use crate::ast::SlotColor;
551 let mut mask = vec![false; self.total_slots];
552 let mut mark = |start: usize, color: SlotColor| match color {
553 SlotColor::Ref2 => {
554 mask[start] = true;
555 mask[start + 1] = true;
556 }
557 SlotColor::Imm1 | SlotColor::Imm2 => {}
558 };
559 for (i, d) in resolved.input_defs.iter().enumerate() {
560 mark(self.input_starts[i], d.port_type.slot_color());
561 }
562 for (n, node) in resolved.nodes.iter().enumerate() {
563 for (p, out) in node.meta().outs.iter().enumerate() {
564 mark(self.port_offsets[n][p], out.typ.slot_color());
565 }
566 }
567 mask
568 }
569
570 fn ref_output_starts(&self, resolved: &ResolvedDag, node_idx: usize) -> Vec<usize> {
574 resolved.nodes[node_idx]
575 .meta()
576 .outs
577 .iter()
578 .enumerate()
579 .filter(|(_, out)| out.typ.slot_color() == crate::ast::SlotColor::Ref2)
580 .map(|(p, _)| self.port_offsets[node_idx][p])
581 .collect()
582 }
583
584 fn expand_dependents(&self, resolved: &ResolvedDag, deps: &[Vec<usize>]) -> Vec<Vec<usize>> {
590 let mut out = Vec::with_capacity(self.coord_slots);
591 for (i, d) in resolved.input_defs.iter().enumerate() {
592 for _ in 0..d.port_type.slot_width() {
593 out.push(deps.get(i).cloned().unwrap_or_default());
594 }
595 }
596 out
597 }
598}
599
600pub struct PolydatAssembler {
602 input_defs: Vec<crate::kernel::InputDef>,
604 coord_count: usize,
606 nodes: Vec<PendingNode>,
607 output_order: Vec<String>,
609 outputs: HashMap<String, WireRef>,
610 source: String,
612 context: String,
614 output_modifiers: HashMap<String, crate::dsl::ast::BindingModifier>,
616 const_inits: Vec<crate::kernel::ConstInit>,
618 shared_starts: Vec<(String, String)>,
621 pub(crate) template: bool,
624 const_outputs: std::collections::HashSet<String>,
628 pub(crate) strict_values: bool,
634 scope_strict_values: Option<bool>,
637 pub(crate) strict: bool,
642 input_variance: crate::dsl::compile::InputVariance,
645 pub(crate) jit_mode: Option<crate::compile::cone::JitMode>,
650 pub(crate) ledger: std::sync::Arc<crate::kernel::CompileLedger>,
654 pub(crate) resources: crate::resource::ResourceScope,
658 cursor_schemas: Vec<crate::iteration::source::SourceSchema>,
662}
663
664type P2Layout = (
668 usize,
669 usize,
670 Vec<crate::compile::closures::P2Step>,
671 HashMap<String, usize>,
672 Vec<bool>,
673 crate::compile::closures::P2Extras,
674);
675
676#[cfg(feature = "jit")]
682type JitLayout = (
683 usize,
684 usize,
685 Vec<(crate::compile::jit::JitOp, Vec<usize>, Vec<usize>)>,
686 HashMap<String, usize>,
687 crate::compile::jit::ScratchPlan,
688 Vec<usize>,
689);
690
691impl PolydatAssembler {
692 pub fn new(input_names: Vec<String>) -> Self {
694 let coord_count = input_names.len();
695 let input_defs: Vec<crate::kernel::InputDef> = input_names
696 .into_iter()
697 .map(|name| crate::kernel::InputDef {
698 name,
699 default: crate::ast::Value::U64(0),
700 port_type: crate::ast::PortType::U64,
701 kind: crate::kernel::InputKind::Coordinate,
702 type_origin: crate::kernel::TypeOrigin::Inferred,
704 converts_to: None,
705 })
706 .collect();
707 Self {
708 input_defs,
709 coord_count,
710 nodes: Vec::new(),
711 output_order: Vec::new(),
712 outputs: HashMap::new(),
713 source: String::new(),
714 context: "(assembler)".into(),
715 output_modifiers: HashMap::new(),
716 const_inits: Vec::new(),
717 shared_starts: Vec::new(),
718 template: false,
719 const_outputs: std::collections::HashSet::new(),
720 strict_values: false,
721 scope_strict_values: None,
722 strict: false,
723 input_variance: crate::dsl::compile::InputVariance::Fixed,
724 jit_mode: None,
725 cursor_schemas: Vec::new(),
726 ledger: crate::kernel::CompileLedger::new(),
727 resources: crate::resource::ResourceScope::new(),
728 }
729 }
730
731 pub fn set_resources(&mut self, resources: crate::resource::ResourceScope) {
737 self.resources = resources;
738 }
739
740 pub fn set_cursor_schemas(&mut self, schemas: Vec<crate::iteration::source::SourceSchema>) {
745 self.cursor_schemas = schemas;
746 }
747
748 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
750 &self.cursor_schemas
751 }
752
753 pub fn set_strict_wires(&mut self, _strict_types: bool, strict_values: bool) {
760 self.strict_values = strict_values;
761 }
762
763 pub fn set_strict(&mut self, strict: bool) {
769 self.strict = strict;
770 }
771
772 pub fn set_jit_mode(&mut self, mode: crate::compile::cone::JitMode) {
776 self.jit_mode = Some(mode);
777 }
778
779 pub fn set_context(&mut self, source: &str, context: &str) {
782 self.source = source.to_string();
783 self.context = context.to_string();
784 }
785
786 pub fn add_node(
788 &mut self,
789 name: impl Into<String>,
790 node: Box<dyn PolydatNode>,
791 inputs: Vec<WireRef>,
792 ) -> &mut Self {
793 self.nodes.push(PendingNode {
794 name: name.into(),
795 node,
796 inputs,
797 strict_values: self.scope_strict_values,
798 });
799 self
800 }
801
802 pub(crate) fn set_scope_strict_values(&mut self, mark: Option<bool>) -> Option<bool> {
810 std::mem::replace(&mut self.scope_strict_values, mark)
811 }
812
813 pub fn set_output_modifier(&mut self, name: &str, modifier: crate::dsl::ast::BindingModifier) {
815 if modifier != crate::dsl::ast::BindingModifier::NONE {
816 self.output_modifiers.insert(name.to_string(), modifier);
817 }
818 }
819
820 pub fn mark_const_output(&mut self, name: &str) {
831 self.const_outputs.insert(name.to_string());
832 }
833
834 pub(crate) fn mark_shared_start(&mut self, name: &str, source: &str) {
840 self.shared_starts
841 .push((name.to_string(), source.to_string()));
842 }
843
844 fn capture_consts(&mut self) -> Result<(), AssemblyError> {
862 use crate::dsl::ast::BindingModifier;
863 use crate::kernel::{InputKind, TypeOrigin};
864
865 let node_of = |nodes: &[PendingNode], name: &str| nodes.iter().position(|n| n.name == name);
866 let cone = |nodes: &[PendingNode], start: &str| -> (Vec<String>, bool) {
870 let by_name: HashMap<&str, usize> = nodes
871 .iter()
872 .enumerate()
873 .rev()
874 .map(|(i, n)| (n.name.as_str(), i))
875 .collect();
876 let mut inputs: Vec<String> = Vec::new();
877 let mut runtime = false;
878 let mut seen = vec![false; nodes.len()];
879 let first = by_name.get(start).copied();
880 let mut stack: Vec<usize> = first.into_iter().collect();
881 while let Some(i) = stack.pop() {
882 if std::mem::replace(&mut seen[i], true) {
883 if Some(i) == first {
886 runtime = true;
887 }
888 continue;
889 }
890 if matches!(
891 nodes[i].node.purity(),
892 crate::ast::Purity::Nondeterministic { .. }
893 ) {
894 runtime = true;
895 }
896 for wire in &nodes[i].inputs {
897 match wire {
898 WireRef::Input(name) => {
899 if !inputs.contains(name) {
900 inputs.push(name.clone());
901 }
902 }
903 WireRef::Node(name, _) => stack.extend(by_name.get(name.as_str())),
904 }
905 }
906 }
907 (inputs, runtime)
908 };
909
910 let mut captured: Vec<(usize, String, String, usize)> = Vec::new();
913 for name in &self.const_outputs {
914 let (node, port) = match self.outputs.get(name) {
915 Some(WireRef::Node(node, port)) => (node.clone(), *port),
916 Some(WireRef::Input(_)) => continue,
917 None if node_of(&self.nodes, name).is_some() => (name.clone(), 0),
918 None => continue,
919 };
920 let Some(order) = node_of(&self.nodes, &node) else {
921 continue;
922 };
923 let (inputs, runtime) = cone(&self.nodes, &node);
924 if inputs.is_empty() && !runtime {
925 continue;
926 }
927 captured.push((order, name.clone(), node, port));
928 }
929 captured.sort();
930
931 let mut records: Vec<(String, String, String, PortType, bool)> = Vec::new();
934 for (_, name, node, port) in captured {
935 let Some(index) = node_of(&self.nodes, &node) else {
936 continue;
937 };
938 let Some(ty) = self.nodes[index].node.meta().outs.get(port).map(|p| p.typ) else {
939 return Err(AssemblyError::UnknownWire(format!("{node}[{port}]")));
940 };
941 let source = format!("__init_{name}");
942 let slot = format!("__const_{name}");
943 let renamed = node == name;
946 let root = if renamed {
947 source.clone()
948 } else {
949 node.clone()
950 };
951 let passthrough = if renamed || node_of(&self.nodes, &name).is_none() {
952 name.clone()
953 } else {
954 slot.clone()
955 };
956 if renamed {
957 self.nodes[index].name = source.clone();
958 }
959 let redirect = |wire: &mut WireRef| {
960 if let WireRef::Node(n, p) = wire
961 && *n == node
962 {
963 if *p == port {
964 *wire = WireRef::node(passthrough.clone());
965 } else {
966 *n = root.clone();
967 }
968 }
969 };
970 for pending in &mut self.nodes {
971 pending.inputs.iter_mut().for_each(redirect);
972 }
973 self.outputs.values_mut().for_each(redirect);
974 self.add_input(&slot, crate::ast::Value::None, ty, InputKind::Const);
975 self.add_node(
976 &passthrough,
977 Box::new(crate::library::identity::PortPassthrough::new(&slot, ty)),
978 vec![WireRef::input(&slot)],
979 );
980 self.add_output(&source, WireRef::node_port(&root, port));
981 let modifier = *self
985 .output_modifiers
986 .entry(name.clone())
987 .or_insert(BindingModifier::CONST);
988 self.set_output_modifier(&source, modifier);
989 records.push((name, slot, source, ty, false));
990 }
991 for (name, source) in std::mem::take(&mut self.shared_starts) {
995 let Some(ty) = self.input_type(&name) else {
996 return Err(AssemblyError::UnknownWire(name));
997 };
998 records.push((name.clone(), name, source, ty, true));
999 }
1000
1001 let slot_owner: HashMap<String, String> = records
1004 .iter()
1005 .map(|(name, slot, _, _, _)| (slot.clone(), name.clone()))
1006 .collect();
1007 let mut pending: Vec<(crate::kernel::ConstInit, Vec<String>)> = Vec::new();
1008 for (name, slot, source, ty, register) in records {
1009 let root = match self.outputs.get(&source) {
1010 Some(WireRef::Node(n, _)) => n.clone(),
1011 _ => return Err(AssemblyError::UnknownWire(source)),
1012 };
1013 let (inputs, _) = cone(&self.nodes, &root);
1014 if let Some(coord) = inputs.iter().find(|i| {
1015 self.input_defs
1016 .iter()
1017 .any(|d| &d.name == *i && d.kind == InputKind::Coordinate)
1018 }) {
1019 let what = if register {
1020 format!("the starting value of shared '{name}'")
1021 } else {
1022 format!("const '{name}'")
1023 };
1024 return Err(AssemblyError::Other(format!(
1025 "{what} reads the coordinate '{coord}': it is evaluated once when the \
1026 kernel is initialized, and a coordinate advances every cycle. Read an \
1027 extern or a const instead."
1028 )));
1029 }
1030 let deps: Vec<String> = inputs
1031 .iter()
1032 .filter_map(|i| slot_owner.get(i))
1033 .filter(|owner| **owner != name)
1034 .cloned()
1035 .collect();
1036 if !register && !inputs.is_empty() && self.input_type(&name).is_none() {
1037 self.add_input(
1038 &name,
1039 crate::ast::Value::None,
1040 ty,
1041 InputKind::IterationExtern,
1042 );
1043 self.set_input_origin(&name, TypeOrigin::Inferred);
1044 }
1045 let fallback = (!register && self.input_type(&name).is_some()).then(|| name.clone());
1046 pending.push((
1047 crate::kernel::ConstInit {
1048 name,
1049 slot,
1050 source,
1051 fallback,
1052 register,
1053 slot_index: 0,
1054 source_index: 0,
1055 fallback_index: None,
1056 },
1057 deps,
1058 ));
1059 }
1060
1061 let mut ordered: Vec<crate::kernel::ConstInit> = Vec::with_capacity(pending.len());
1063 while !pending.is_empty() {
1064 let (ready, rest): (Vec<_>, Vec<_>) = pending.into_iter().partition(|(_, deps)| {
1065 deps.iter().all(|d| {
1066 ordered
1067 .iter()
1068 .any(|c: &crate::kernel::ConstInit| &c.name == d)
1069 })
1070 });
1071 if ready.is_empty() {
1072 let names: Vec<&str> = rest.iter().map(|(c, _)| c.name.as_str()).collect();
1073 return Err(AssemblyError::Other(format!(
1074 "the consts {names:?} read each other in a cycle, so none can be evaluated \
1075 first"
1076 )));
1077 }
1078 ordered.extend(ready.into_iter().map(|(c, _)| c));
1079 pending = rest;
1080 }
1081 let input_index = |name: &str| self.input_defs.iter().position(|d| d.name == name);
1082 for c in &mut ordered {
1083 c.slot_index = input_index(&c.slot).expect("the const's slot was just added");
1084 c.source_index = self
1085 .output_order
1086 .iter()
1087 .position(|n| n == &c.source)
1088 .expect("the const's source output was just added");
1089 c.fallback_index = c.fallback.as_deref().and_then(input_index);
1090 }
1091 self.const_inits = ordered;
1092 Ok(())
1093 }
1094
1095 pub fn node_count(&self) -> usize {
1097 self.nodes.len()
1098 }
1099
1100 pub fn add_output(&mut self, name: impl Into<String>, wire: WireRef) -> &mut Self {
1102 let name = name.into();
1103 if !self.outputs.contains_key(&name) {
1104 self.output_order.push(name.clone());
1105 }
1106 self.outputs.insert(name, wire);
1107 self
1108 }
1109
1110 pub fn add_input(
1121 &mut self,
1122 name: impl Into<String>,
1123 default: crate::ast::Value,
1124 port_type: crate::ast::PortType,
1125 kind: crate::kernel::InputKind,
1126 ) -> &mut Self {
1127 self.input_defs.push(crate::kernel::InputDef {
1128 name: name.into(),
1129 default,
1130 port_type,
1131 kind,
1132 type_origin: crate::kernel::TypeOrigin::Declared,
1133 converts_to: None,
1134 });
1135 self
1136 }
1137
1138 pub fn set_input_type(&mut self, name: &str, port_type: crate::ast::PortType) {
1143 if let Some(d) = self.input_defs.iter_mut().find(|d| d.name == name) {
1144 d.port_type = port_type;
1145 d.type_origin = crate::kernel::TypeOrigin::Declared;
1146 }
1147 }
1148
1149 pub fn set_input_origin(&mut self, name: &str, origin: crate::kernel::TypeOrigin) {
1153 if let Some(d) = self.input_defs.iter_mut().find(|d| d.name == name) {
1154 d.type_origin = origin;
1155 }
1156 }
1157
1158 pub fn set_input_variance(&mut self, variance: crate::dsl::compile::InputVariance) {
1160 self.input_variance = variance;
1161 }
1162
1163 pub fn input_names(&self) -> Vec<&str> {
1165 self.input_defs.iter().map(|d| d.name.as_str()).collect()
1166 }
1167
1168 pub fn node_output_type(&self, name: &str) -> Option<crate::ast::PortType> {
1173 self.nodes
1174 .iter()
1175 .find(|n| n.name == name)
1176 .and_then(|n| n.node.meta().outs.first())
1177 .map(|p| p.typ)
1178 }
1179
1180 pub fn output_names(&self) -> Vec<&str> {
1182 self.outputs.keys().map(|s| s.as_str()).collect()
1183 }
1184
1185 pub fn node_type_of(&self, name: &str) -> Option<String> {
1187 self.nodes
1188 .iter()
1189 .find(|pn| pn.name == name)
1190 .map(|pn| pn.node.meta().name.clone())
1191 }
1192
1193 pub fn output_type(&self, name: &str) -> Option<PortType> {
1197 self.nodes
1198 .iter()
1199 .find(|pn| pn.name == name)
1200 .and_then(|pn| pn.node.meta().outs.first())
1201 .map(|port| port.typ)
1202 }
1203
1204 pub fn input_type(&self, name: &str) -> Option<PortType> {
1206 self.input_defs
1207 .iter()
1208 .find(|d| d.name == name)
1209 .map(|d| d.port_type)
1210 }
1211
1212 pub fn wire_type(&self, wire: &WireRef) -> Option<PortType> {
1217 match wire {
1218 WireRef::Input(name) => self.input_type(name),
1219 WireRef::Node(name, port_idx) => self
1220 .nodes
1221 .iter()
1222 .find(|pn| &pn.name == name)
1223 .and_then(|pn| pn.node.meta().outs.get(*port_idx))
1224 .map(|p| p.typ),
1225 }
1226 }
1227
1228 pub fn compile(self) -> Result<PolydatKernel, AssemblyError> {
1230 self.compile_with_log(None)
1231 }
1232
1233 pub fn compile_with_log(
1235 self,
1236 mut log: Option<&mut crate::dsl::events::CompileEventLog>,
1237 ) -> Result<PolydatKernel, AssemblyError> {
1238 let jit_mode = self.jit_mode.unwrap_or_default();
1239 let strict = self.strict;
1240 let template = self.template;
1241 let mut resolved = self.resolve_with_log(log.as_deref_mut())?;
1242 let (node_total, output_total) = (resolved.nodes.len(), resolved.output_order.len());
1243 let identity = resolved.graph_identity();
1244 crate::compile::cone::extract_jit_cones(&mut resolved, jit_mode)?;
1245 let _coord_names = resolved.input_names();
1246 let modifiers = resolved.output_modifiers.clone();
1247 let cursors = std::mem::take(&mut resolved.cursor_schemas);
1248 let mut kernel = PolydatKernel::new_with_inputs(
1249 resolved.nodes,
1250 resolved.wiring,
1251 resolved.input_defs,
1252 resolved.coord_count,
1253 resolved.output_map,
1254 resolved.output_order,
1255 resolved.const_outputs,
1256 modifiers,
1257 &resolved.source,
1258 &resolved.context,
1259 log.as_deref_mut(),
1260 strict,
1261 resolved.ledger.clone(),
1262 )?;
1263 kernel.set_resources(resolved.resources.clone());
1264 kernel.set_graph_identity(identity);
1265 if !cursors.is_empty() {
1266 kernel.set_cursor_schemas(cursors);
1267 }
1268 kernel.set_cone_mode(jit_mode);
1269 kernel.set_const_inits(resolved.const_inits);
1270 Self::log_summary(log, node_total, output_total);
1271 if template {
1272 return Ok(kernel);
1273 }
1274 crate::kernel::Kernel::init(&mut kernel).map_err(|e| match e {
1275 KernelError::ConstInit { name, reason } => AssemblyError::ConstInit { name, reason },
1276 other => AssemblyError::ConstInit {
1277 name: String::new(),
1278 reason: other.to_string(),
1279 },
1280 })?;
1281 Ok(kernel)
1282 }
1283
1284 fn refuse_strict(resolved: &ResolvedDag) -> Result<(), AssemblyError> {
1288 let classes = PolydatProgram::classify_lifecycle(
1289 &resolved.nodes,
1290 &resolved.wiring,
1291 &resolved.input_defs,
1292 &resolved.output_map,
1293 &resolved.output_modifiers,
1294 );
1295 let is_init: Vec<bool> = classes
1296 .lifecycle
1297 .iter()
1298 .map(|lc| *lc == crate::kernel::EvalLifecycle::CompileConst)
1299 .collect();
1300 match PolydatProgram::strict_violation(
1301 &resolved.nodes,
1302 &resolved.wiring,
1303 &is_init,
1304 &resolved.output_map,
1305 &resolved.output_modifiers,
1306 ) {
1307 Some(violation) => Err(AssemblyError::Other(violation)),
1308 None => Ok(()),
1309 }
1310 }
1311
1312 fn refused_by_closures(reason: String) -> KernelError {
1315 KernelError::Refused {
1316 engine: Engine::Closures(Provenance::Auto),
1317 reason,
1318 }
1319 }
1320
1321 fn refused_by_native(reason: String) -> KernelError {
1324 KernelError::Refused {
1325 engine: Engine::Native(Provenance::Auto),
1326 reason,
1327 }
1328 }
1329
1330 #[cfg_attr(not(feature = "jit"), allow(dead_code))]
1335 fn refused_by_pure_native(reason: String) -> KernelError {
1336 KernelError::Refused {
1337 engine: Engine::PureNative(Provenance::Auto),
1338 reason,
1339 }
1340 }
1341
1342 fn build_p2_layout(resolved: &ResolvedDag) -> Result<P2Layout, String> {
1345 let layout = slot_layout(resolved);
1346
1347 let mut compiled_ops = Vec::with_capacity(resolved.nodes.len());
1348 let mut extras = crate::compile::closures::P2Extras::default();
1349 for (node_idx, node) in resolved.nodes.iter().enumerate() {
1350 compiled_ops.push(
1351 node_step_op(node.as_ref(), &wire_types_of(resolved, node_idx)).ok_or_else(
1352 || {
1353 format!(
1354 "node '{}' has no compiled form (docs/design/engines.md §8)",
1355 node.meta().name
1356 )
1357 },
1358 )?,
1359 );
1360 }
1361 extras.externs = crate::compile::externs::Externs::new(
1362 &resolved.input_defs,
1363 resolved.coord_count,
1364 &layout.input_starts,
1365 &resolved.cursor_schemas,
1366 &shared_outputs_of(resolved),
1367 resolved.ledger.clone(),
1368 )?;
1369 extras.externs.set_resources(resolved.resources.clone());
1370 extras.externs.set_graph_identity(resolved.graph_identity());
1371 extras.externs.set_output_names(&resolved.output_order);
1372 extras
1373 .externs
1374 .set_output_modifiers(&resolved.output_modifiers);
1375 extras.externs.set_const_inits(&resolved.const_inits);
1376 extras.externs.set_fixed_outputs(fixed_outputs(resolved));
1377 extras.output_types = resolved
1378 .output_map
1379 .iter()
1380 .map(|(name, (n, p))| (name.clone(), resolved.nodes[*n].meta().outs[*p].typ))
1381 .collect();
1382
1383 let classes = PolydatProgram::classify_lifecycle(
1387 &resolved.nodes,
1388 &resolved.wiring,
1389 &resolved.input_defs,
1390 &resolved.output_map,
1391 &resolved.output_modifiers,
1392 );
1393 let inventory = PolydatProgram::compute_node_inventory(&resolved.nodes, &resolved.wiring);
1394 let per_input = PolydatProgram::compute_dependents(
1395 &inventory.input_provenance,
1396 resolved.input_defs.len(),
1397 );
1398 extras.input_dependents = layout.expand_dependents(resolved, &per_input);
1399 extras.attribution = std::sync::Arc::new(Self::attribution_of(resolved));
1400
1401 let mut steps = Vec::with_capacity(resolved.nodes.len());
1402 for (node_idx, (op, scratch)) in compiled_ops.into_iter().enumerate() {
1403 steps.push(crate::compile::closures::P2Step {
1404 name: resolved.nodes[node_idx].meta().name.clone(),
1405 op,
1406 input_slots: layout.input_slots(resolved, node_idx),
1407 output_slots: layout.output_slots(resolved, node_idx),
1408 ref_output_starts: layout.ref_output_starts(resolved, node_idx),
1409 scratch,
1410 accepts_none: resolved.nodes[node_idx].accepts_none_inputs(),
1411 volatile: classes.nondeterministic[node_idx],
1412 constant: classes.lifecycle[node_idx] == crate::kernel::EvalLifecycle::CompileConst,
1413 side: matches!(
1414 resolved.nodes[node_idx].purity(),
1415 crate::ast::Purity::SideChannel { .. }
1416 ),
1417 });
1418 }
1419 let output_map = layout.named_outputs(resolved);
1420 let ref_slots = layout.ref_slot_mask(resolved);
1421
1422 Ok((
1423 layout.coord_slots,
1424 layout.total_slots,
1425 steps,
1426 output_map,
1427 ref_slots,
1428 extras,
1429 ))
1430 }
1431
1432 #[cfg(feature = "jit")]
1434 pub(crate) fn build_jit_layout(resolved: &ResolvedDag) -> Result<JitLayout, String> {
1435 let layout = slot_layout(resolved);
1436
1437 let mut scratch = crate::compile::jit::ScratchPlan::default();
1441 let mut jit_steps = Vec::new();
1442 for (node_idx, node) in resolved.nodes.iter().enumerate() {
1443 let mut jit_op = crate::compile::jit::classify_node_typed(
1444 node.as_ref(),
1445 &wire_types_of(resolved, node_idx),
1446 );
1447 if matches!(jit_op, crate::compile::jit::JitOp::Fallback) {
1448 return Err(format!(
1449 "node '{}' has no native form and no kit; pure native code cannot run it",
1450 node.meta().name
1451 ));
1452 }
1453 let base = scratch.elems.len();
1454 jit_op.place_scratch(base);
1455 let elems = jit_op.scratch_elems().to_vec();
1456 scratch.refs.extend(scratch_pairs(
1457 &node.meta().name,
1458 &layout.ref_output_starts(resolved, node_idx),
1459 &elems,
1460 base,
1461 ));
1462 scratch.elems.extend(elems);
1463 jit_steps.push((
1464 jit_op,
1465 layout.input_slots(resolved, node_idx),
1466 layout.output_slots(resolved, node_idx),
1467 ));
1468 }
1469
1470 let output_map = layout.named_outputs(resolved);
1471 let classes = PolydatProgram::classify_lifecycle(
1475 &resolved.nodes,
1476 &resolved.wiring,
1477 &resolved.input_defs,
1478 &resolved.output_map,
1479 &resolved.output_modifiers,
1480 );
1481 let volatile: Vec<usize> = (0..resolved.nodes.len())
1482 .filter(|&i| classes.nondeterministic[i])
1483 .collect();
1484 Ok((
1485 layout.coord_slots,
1486 layout.total_slots,
1487 jit_steps,
1488 output_map,
1489 scratch,
1490 volatile,
1491 ))
1492 }
1493
1494 #[cfg(feature = "jit")]
1498 fn jit_slot_info(resolved: &ResolvedDag) -> (Vec<bool>, HashMap<String, PortType>) {
1499 let layout = slot_layout(resolved);
1500 let guard = layout.ref_slot_mask(resolved);
1501 let types = resolved
1502 .output_map
1503 .iter()
1504 .map(|(name, (n, p))| (name.clone(), resolved.nodes[*n].meta().outs[*p].typ))
1505 .collect();
1506 (guard, types)
1507 }
1508
1509 #[cfg(feature = "jit")]
1510 fn jit_push_pull_from(
1511 resolved: ResolvedDag,
1512 ) -> Result<crate::compile::jit::JitKernelPushPull, KernelError> {
1513 let _coord_names = resolved.input_names();
1514 let (coord_count, total_slots, jit_steps, output_map, scratch, volatile) =
1515 Self::build_jit_layout(&resolved).map_err(Self::refused_by_pure_native)?;
1516 let (guard, types) = Self::jit_slot_info(&resolved);
1517 let deps = slot_layout(&resolved).expand_dependents(
1518 &resolved,
1519 &PolydatProgram::compute_dependents(
1520 &PolydatProgram::compute_provenance(&resolved.nodes, &resolved.wiring),
1521 resolved.input_defs.len(),
1522 ),
1523 );
1524 let externs = Self::externs_of(&resolved).map_err(Self::refused_by_pure_native)?;
1525 let attribution = std::sync::Arc::new(Self::attribution_of(&resolved));
1526 let (folded, origin) = Self::constant_steps(&resolved, &jit_steps);
1527 let alone = Self::side_channels(&resolved);
1528 let mut k = crate::compile::jit::compile_jit_push_pull(
1529 coord_count,
1530 total_slots,
1531 jit_steps,
1532 output_map,
1533 resolved.nodes,
1534 deps,
1535 externs,
1536 scratch,
1537 volatile,
1538 alone,
1539 )
1540 .map_err(Self::refused_by_pure_native)?;
1541 k.set_slot_info(guard, types);
1542 k.set_attribution(attribution);
1543 k.fold_constants(&folded, &origin, total_slots)?;
1546 Ok(k)
1547 }
1548
1549 #[cfg(feature = "jit")]
1557 #[allow(clippy::type_complexity)]
1558 fn constant_steps(
1559 resolved: &ResolvedDag,
1560 jit_steps: &[(crate::compile::jit::JitOp, Vec<usize>, Vec<usize>)],
1561 ) -> (
1562 Vec<(crate::compile::jit::JitOp, Vec<usize>, Vec<usize>)>,
1563 Vec<usize>,
1564 ) {
1565 let classes = PolydatProgram::classify_lifecycle(
1566 &resolved.nodes,
1567 &resolved.wiring,
1568 &resolved.input_defs,
1569 &resolved.output_map,
1570 &resolved.output_modifiers,
1571 );
1572 jit_steps
1575 .iter()
1576 .enumerate()
1577 .filter(|(i, _)| {
1578 classes.lifecycle.get(*i) == Some(&crate::kernel::EvalLifecycle::CompileConst)
1579 })
1580 .map(|(i, s)| (s.clone(), i))
1581 .unzip()
1582 }
1583
1584 #[cfg(feature = "jit")]
1588 fn side_channels(resolved: &ResolvedDag) -> Vec<bool> {
1589 resolved
1590 .nodes
1591 .iter()
1592 .map(|n| matches!(n.purity(), crate::ast::Purity::SideChannel { .. }))
1593 .collect()
1594 }
1595
1596 fn externs_of(resolved: &ResolvedDag) -> Result<crate::compile::externs::Externs, String> {
1601 let layout = slot_layout(resolved);
1602 let mut externs = crate::compile::externs::Externs::new(
1603 &resolved.input_defs,
1604 resolved.coord_count,
1605 &layout.input_starts,
1606 &resolved.cursor_schemas,
1607 &shared_outputs_of(resolved),
1608 resolved.ledger.clone(),
1609 )?;
1610 externs.set_resources(resolved.resources.clone());
1611 externs.set_graph_identity(resolved.graph_identity());
1612 externs.set_output_names(&resolved.output_order);
1613 externs.set_output_modifiers(&resolved.output_modifiers);
1614 externs.set_const_inits(&resolved.const_inits);
1615 externs.set_fixed_outputs(fixed_outputs(resolved));
1616 Ok(externs)
1617 }
1618
1619 #[doc(hidden)]
1621 #[cfg(feature = "jit")]
1622 pub(crate) fn try_compile_pure_jit_raw(
1623 self,
1624 ) -> Result<crate::compile::jit::JitKernelRaw, KernelError> {
1625 let resolved = self.resolve().map_err(KernelError::Assembly)?;
1626 Self::jit_raw_from(resolved)
1627 }
1628
1629 #[cfg(feature = "bench-tiers")]
1654 pub fn compile_closures_raw(
1655 self,
1656 ) -> Result<crate::compile::closures::CompiledKernelRaw, KernelError> {
1657 let resolved = self.resolve_with_log(None)?;
1658 let (coord_count, total_slots, steps, output_map, ref_slots, extras) =
1659 Self::build_p2_layout(&resolved).map_err(Self::refused_by_closures)?;
1660 crate::compile::closures::CompiledKernelRaw::new(
1661 coord_count,
1662 total_slots,
1663 steps,
1664 output_map,
1665 ref_slots,
1666 extras,
1667 )
1668 }
1669
1670 #[cfg(all(feature = "bench-tiers", feature = "jit"))]
1672 pub fn compile_native_raw(
1673 self,
1674 ) -> Result<crate::compile::hybrid::HybridKernelRaw, KernelError> {
1675 let resolved = self.resolve_with_log(None)?;
1676 Ok(Self::hybrid_from(resolved)?.into_raw())
1677 }
1678
1679 #[cfg(all(feature = "bench-tiers", feature = "jit"))]
1681 pub fn compile_pure_native_raw(self) -> Result<crate::compile::jit::JitKernelRaw, KernelError> {
1682 self.try_compile_pure_jit_raw()
1683 }
1684
1685 pub(crate) fn attribution_of(resolved: &ResolvedDag) -> crate::compile::Attribution {
1690 let layout = slot_layout(resolved);
1691 let sites = resolved
1692 .nodes
1693 .iter()
1694 .enumerate()
1695 .map(|(node_idx, node)| {
1696 let mut outputs: Vec<String> = resolved
1697 .output_map
1698 .iter()
1699 .filter(|(_, (n, _))| *n == node_idx)
1700 .map(|(name, _)| name.clone())
1701 .collect();
1702 outputs.sort();
1703 let inputs = resolved.wiring[node_idx]
1704 .iter()
1705 .map(|source| match source {
1706 WireSource::Input(c) => (
1707 layout.input_starts.get(*c).copied().unwrap_or(*c),
1708 resolved
1709 .input_defs
1710 .get(*c)
1711 .map(|d| d.port_type)
1712 .unwrap_or(PortType::U64),
1713 ),
1714 WireSource::NodeOutput(u, p) => (
1715 layout.port_offsets[*u][*p],
1716 resolved.nodes[*u].meta().outs[*p].typ,
1717 ),
1718 })
1719 .collect();
1720 crate::compile::NodeSite {
1721 name: node.meta().name.to_string(),
1722 outputs,
1723 inputs,
1724 }
1725 })
1726 .collect();
1727 crate::compile::Attribution {
1728 sites,
1729 context: resolved.context.clone(),
1730 }
1731 }
1732
1733 #[cfg(feature = "jit")]
1734 fn jit_raw_from(
1735 resolved: ResolvedDag,
1736 ) -> Result<crate::compile::jit::JitKernelRaw, KernelError> {
1737 let _coord_names = resolved.input_names();
1738 let (coord_count, total_slots, jit_steps, output_map, scratch, volatile) =
1739 Self::build_jit_layout(&resolved).map_err(Self::refused_by_pure_native)?;
1740 let (guard, types) = Self::jit_slot_info(&resolved);
1741 let externs = Self::externs_of(&resolved).map_err(Self::refused_by_pure_native)?;
1742 let attribution = std::sync::Arc::new(Self::attribution_of(&resolved));
1743 let (folded, origin) = Self::constant_steps(&resolved, &jit_steps);
1744 let alone = Self::side_channels(&resolved);
1745 let mut k = crate::compile::jit::compile_jit_raw_with(
1746 coord_count,
1747 total_slots,
1748 jit_steps,
1749 output_map,
1750 resolved.nodes,
1751 externs,
1752 scratch,
1753 volatile,
1754 alone,
1755 )
1756 .map_err(Self::refused_by_pure_native)?;
1757 k.set_slot_info(guard, types);
1758 k.set_attribution(attribution);
1759 k.fold_constants(&folded, &origin, total_slots)?;
1762 Ok(k)
1763 }
1764
1765 #[cfg(feature = "jit")]
1771 #[doc(hidden)]
1772 pub fn try_compile_tier1_simd_ordinal(
1773 self,
1774 driving_input: &str,
1775 output: &str,
1776 ) -> Result<
1777 crate::compile::simd_tier1::Tier1SimdExecutor,
1778 crate::compile::simd_tier1::Tier1SimdError,
1779 > {
1780 let resolved = self.resolve().map_err(|error| {
1781 crate::compile::simd_tier1::Tier1SimdError::VectorGraphBuild(error.to_string())
1782 })?;
1783 crate::compile::simd_tier1::compile_tier1_ordinal(resolved, driving_input, output)
1784 }
1785
1786 fn hybrid_from(
1787 resolved: ResolvedDag,
1788 ) -> Result<crate::compile::hybrid::HybridKernel, KernelError> {
1789 let _coord_names = resolved.input_names();
1790 let layout = slot_layout(&resolved);
1791
1792 let output_map = layout.named_outputs(&resolved);
1793 let input_widths: Vec<usize> = resolved
1794 .input_defs
1795 .iter()
1796 .map(|d| d.port_type.slot_width())
1797 .collect();
1798
1799 let ref_slots = layout.ref_slot_mask(&resolved);
1800 let input_types: Vec<PortType> = resolved.input_defs.iter().map(|d| d.port_type).collect();
1801 let externs = Self::externs_of(&resolved).map_err(Self::refused_by_native)?;
1802 let attribution = std::sync::Arc::new(Self::attribution_of(&resolved));
1803 let classes = PolydatProgram::classify_lifecycle(
1806 &resolved.nodes,
1807 &resolved.wiring,
1808 &resolved.input_defs,
1809 &resolved.output_map,
1810 &resolved.output_modifiers,
1811 );
1812 let constant: Vec<bool> = classes
1813 .lifecycle
1814 .iter()
1815 .map(|lc| *lc == crate::kernel::EvalLifecycle::CompileConst)
1816 .collect();
1817 let mut kernel = crate::compile::hybrid::build_hybrid(
1818 &resolved.nodes,
1819 &resolved.wiring,
1820 layout.coord_slots,
1821 layout.total_slots,
1822 &layout.port_offsets,
1823 &layout.input_starts,
1824 &input_widths,
1825 output_map,
1826 ref_slots,
1827 &input_types,
1828 externs,
1829 constant,
1830 classes.nondeterministic,
1831 attribution,
1832 )?;
1833 kernel.retain_nodes(resolved.nodes);
1834 Ok(kernel)
1835 }
1836
1837 fn log_forms(resolved: &ResolvedDag, log: &mut crate::dsl::events::CompileEventLog) {
1845 for (node_idx, node) in resolved.nodes.iter().enumerate() {
1846 let wire_types = wire_types_of(resolved, node_idx);
1847 #[cfg(feature = "jit")]
1851 let native = !matches!(
1852 crate::compile::jit::classify_node_typed(node.as_ref(), &wire_types),
1853 crate::compile::jit::JitOp::Fallback
1854 );
1855 #[cfg(not(feature = "jit"))]
1856 let native = false;
1857 let level = if native {
1858 "native"
1859 } else {
1860 match node_step_op(node.as_ref(), &wire_types) {
1861 Some((crate::compile::closures::StepOp::Copy, _)) => "slot copy",
1862 Some((crate::compile::closures::StepOp::U64(_), _)) => "compiled u64 op",
1863 Some((crate::compile::closures::StepOp::Slot(_), _)) => "slot kit",
1864 None => "interpreted",
1865 }
1866 };
1867 let name = resolved
1868 .output_map
1869 .iter()
1870 .find(|(_, (ni, _))| *ni == node_idx)
1871 .map(|(n, _)| n.clone())
1872 .unwrap_or_else(|| node.meta().name.clone());
1873 log.push(crate::dsl::events::CompileEvent::CompileLevelSelected {
1874 node: name,
1875 level: level.to_string(),
1876 });
1877 }
1878 }
1879
1880 fn log_summary(
1885 log: Option<&mut crate::dsl::events::CompileEventLog>,
1886 nodes: usize,
1887 outputs: usize,
1888 ) {
1889 if let Some(log) = log {
1890 let constants_folded = log
1891 .events()
1892 .iter()
1893 .filter(|e| matches!(e, crate::dsl::events::CompileEvent::ConstantFolded { .. }))
1894 .count();
1895 log.push(crate::dsl::events::CompileEvent::Summary {
1896 nodes,
1897 outputs,
1898 constants_folded,
1899 });
1900 }
1901 }
1902
1903 #[cfg(feature = "jit")]
1906 fn resolve(self) -> Result<ResolvedDag, AssemblyError> {
1907 self.resolve_with_log(None)
1908 }
1909
1910 fn resolve_with_log(
1911 mut self,
1912 mut log: Option<&mut crate::dsl::events::CompileEventLog>,
1913 ) -> Result<ResolvedDag, AssemblyError> {
1914 self.capture_consts()?;
1917 let open: Vec<usize> = self
1924 .input_defs
1925 .iter()
1926 .enumerate()
1927 .filter(|(_, d)| {
1928 d.kind != crate::kernel::InputKind::Coordinate
1929 && d.type_origin == crate::kernel::TypeOrigin::Inferred
1930 && d.port_type != PortType::Dyn
1931 })
1932 .map(|(i, _)| i)
1933 .collect();
1934 let variance_level = match self.input_variance {
1935 crate::dsl::compile::InputVariance::Fixed => None,
1936 crate::dsl::compile::InputVariance::Error => {
1937 if !open.is_empty() {
1938 return Err(AssemblyError::OpenInputs(
1939 open.iter()
1940 .map(|&i| {
1941 (
1942 self.input_defs[i].name.clone(),
1943 self.input_defs[i].port_type,
1944 )
1945 })
1946 .collect(),
1947 ));
1948 }
1949 None
1950 }
1951 crate::dsl::compile::InputVariance::Warn => {
1952 Some(crate::dsl::events::EventLevel::Warning)
1953 }
1954 crate::dsl::compile::InputVariance::Info => Some(crate::dsl::events::EventLevel::Info),
1955 };
1956 if variance_level.is_some() {
1957 for &i in &open {
1958 let def = &mut self.input_defs[i];
1959 def.converts_to = Some(def.port_type);
1960 def.port_type = PortType::Dyn;
1961 }
1962 }
1963 if let Some(log) = log.as_deref_mut() {
1969 let cursor_slot = |name: &str| {
1970 self.cursor_schemas
1971 .iter()
1972 .any(|s| name.starts_with(&format!("{}__cursor", s.name)))
1973 };
1974 for def in &self.input_defs {
1975 if matches!(
1976 def.kind,
1977 crate::kernel::InputKind::ExternalWrite
1978 | crate::kernel::InputKind::IterationExtern
1979 ) && def.default == crate::ast::Value::None
1980 && !cursor_slot(&def.name)
1981 {
1982 log.push(crate::dsl::events::CompileEvent::ExternWithoutDefault {
1983 name: def.name.clone(),
1984 port_type: def.port_type.to_string(),
1985 });
1986 }
1987 }
1988 }
1989 let mut name_to_idx: HashMap<String, usize> = HashMap::new();
1991 for (i, pn) in self.nodes.iter().enumerate() {
1992 if name_to_idx.contains_key(&pn.name) {
1993 return Err(AssemblyError::DuplicateNode(pn.name.clone()));
1994 }
1995 name_to_idx.insert(pn.name.clone(), i);
1996 }
1997
1998 let input_to_idx: HashMap<String, usize> = self
2000 .input_defs
2001 .iter()
2002 .enumerate()
2003 .map(|(i, d)| (d.name.clone(), i))
2004 .collect();
2005
2006 for pn in &self.nodes {
2008 let expected = pn.node.meta().wire_inputs().len();
2009 let got = pn.inputs.len();
2010 if expected != got {
2011 return Err(AssemblyError::ArityMismatch {
2012 node_name: pn.name.clone(),
2013 expected,
2014 got,
2015 });
2016 }
2017 }
2018
2019 let mut all_nodes: Vec<PendingNode> = Vec::new();
2020 let mut all_name_to_idx: HashMap<String, usize> = HashMap::new();
2021 let mut adapter_count = 0usize;
2022 let mut assertion_count = 0usize;
2023 let strict_values = self.strict_values;
2024 let strict = self.strict;
2025 let mut inserted_guards: HashMap<usize, crate::dsl::const_constraints::ConstConstraint> =
2029 HashMap::new();
2030 let volatile_nodes: std::collections::HashSet<String> = self
2033 .output_modifiers
2034 .iter()
2035 .filter(|(_, m)| m.is_volatile())
2036 .filter_map(|(name, _)| match self.outputs.get(name) {
2037 Some(WireRef::Node(node, _)) => Some(node.clone()),
2038 _ => None,
2039 })
2040 .collect();
2041
2042 for pn in self.nodes {
2043 let idx = all_nodes.len();
2044 all_name_to_idx.insert(pn.name.clone(), idx);
2045 all_nodes.push(pn);
2046 }
2047
2048 let mut resolved_wiring: Vec<Vec<WireSource>> = Vec::new();
2049 let mut converters: HashMap<(usize, PortType), usize> = HashMap::new();
2051
2052 for node_idx in 0..all_nodes.len() {
2053 let mut node_wiring = Vec::new();
2054
2055 for (port_idx, wire_ref) in all_nodes[node_idx].inputs.clone().iter().enumerate() {
2056 let port = all_nodes[node_idx].node.meta().wire_inputs()[port_idx].clone();
2057 let expected_type = port.typ;
2058
2059 let (source, source_type) = match wire_ref {
2060 WireRef::Input(name) => {
2061 let input_idx = input_to_idx
2062 .get(name)
2063 .ok_or_else(|| AssemblyError::UnknownWire(name.clone()))?;
2064 let source_type = self.input_defs[*input_idx].port_type;
2065 (WireSource::Input(*input_idx), source_type)
2066 }
2067 WireRef::Node(name, out_port) => {
2068 let src_idx = all_name_to_idx
2069 .get(name)
2070 .ok_or_else(|| AssemblyError::UnknownWire(name.clone()))?;
2071 let src_type = all_nodes[*src_idx].node.meta().outs[*out_port].typ;
2072 (WireSource::NodeOutput(*src_idx, *out_port), src_type)
2073 }
2074 };
2075
2076 if source_type == PortType::Dyn
2087 && !port.accepts_any_type
2088 && expected_type != PortType::Dyn
2089 {
2090 let WireSource::Input(input_idx) = source else {
2091 unreachable!("only an input slot is typed `Dyn`")
2092 };
2093 let conv_idx = match converters.get(&(input_idx, expected_type)) {
2094 Some(&idx) => idx,
2095 None => {
2096 let converter = crate::convert::InputConverter::new(
2097 &self.input_defs[input_idx].name,
2098 expected_type,
2099 );
2100 let conv_name = converter.meta().name.clone();
2101 let idx = all_nodes.len();
2102 all_name_to_idx.insert(conv_name.clone(), idx);
2103 while resolved_wiring.len() <= idx {
2104 resolved_wiring.push(Vec::new());
2105 }
2106 resolved_wiring[idx] = vec![source.clone()];
2107 all_nodes.push(PendingNode {
2108 name: conv_name,
2109 node: Box::new(converter),
2110 inputs: vec![],
2111 strict_values: None,
2112 });
2113 converters.insert((input_idx, expected_type), idx);
2114 idx
2115 }
2116 };
2117 node_wiring.push(WireSource::NodeOutput(conv_idx, 0));
2118 } else if port.accepts_any_type || source_type == expected_type {
2119 node_wiring.push(source);
2120 } else if let Some(adapter) = auto_adapter(source_type, expected_type) {
2121 if strict {
2122 return Err(AssemblyError::Other(format!(
2123 "strict mode: implicit type coercion {source_type} → {expected_type} \
2124 into '{}'. Use an explicit conversion function (e.g., to_f64, \
2125 to_i64, f64_to_u64).",
2126 all_nodes[node_idx].name
2127 )));
2128 }
2129 let adapter_name = format!("__adapt_{adapter_count}");
2130 adapter_count += 1;
2131 let adapter_idx = all_nodes.len();
2132
2133 if let Some(ref mut log) = log {
2134 let from_name = match wire_ref {
2135 WireRef::Input(n) => n.clone(),
2136 WireRef::Node(n, _) => n.clone(),
2137 };
2138 let to_name = all_nodes[node_idx].name.clone();
2139 log.push(if is_lossless_widening(source_type, expected_type) {
2140 crate::dsl::events::CompileEvent::TypeWidening {
2141 from: source_type.to_keyword(),
2142 to: expected_type.to_keyword(),
2143 context: format!("{from_name} → {to_name}"),
2144 }
2145 } else {
2146 crate::dsl::events::CompileEvent::TypeAdapterInserted {
2147 from_node: from_name,
2148 to_node: to_name,
2149 adapter: format!("{source_type:?}→{expected_type:?}"),
2150 }
2151 });
2152 }
2153
2154 all_name_to_idx.insert(adapter_name.clone(), adapter_idx);
2155
2156 let adapter_wiring = vec![source];
2157 while resolved_wiring.len() <= adapter_idx {
2158 resolved_wiring.push(Vec::new());
2159 }
2160 resolved_wiring[adapter_idx] = adapter_wiring;
2161
2162 let sink_mark = all_nodes[node_idx].strict_values;
2166 all_nodes.push(PendingNode {
2167 name: adapter_name,
2168 node: adapter,
2169 inputs: vec![],
2170 strict_values: sink_mark,
2171 });
2172
2173 node_wiring.push(WireSource::NodeOutput(adapter_idx, 0));
2174 } else {
2175 let from_name = match wire_ref {
2176 WireRef::Input(n) => n.clone(),
2177 WireRef::Node(n, _) => n.clone(),
2178 };
2179 return Err(AssemblyError::TypeMismatch {
2180 from_node: from_name,
2181 from_port: match wire_ref {
2182 WireRef::Input(_) => 0,
2183 WireRef::Node(_, p) => *p,
2184 },
2185 from_type: source_type,
2186 to_node: all_nodes[node_idx].name.clone(),
2187 to_port: port_idx,
2188 to_type: expected_type,
2189 });
2190 }
2191
2192 let sink_port = &all_nodes[node_idx].node.meta().wire_inputs()[port_idx];
2205 if let Some(constraint) = sink_port.constraint {
2206 let last_source = node_wiring.last().expect("wire just pushed").clone();
2207 let proof = if all_nodes[node_idx].strict_values.unwrap_or(strict_values) {
2208 constraint_proof(
2209 &all_nodes,
2210 &inserted_guards,
2211 &volatile_nodes,
2212 &last_source,
2213 &constraint,
2214 )
2215 } else {
2216 ConstraintProof::NotChecked
2217 };
2218 if let ConstraintProof::Violated { value, message } = &proof {
2219 let sink = &all_nodes[node_idx];
2220 let detail = match constraint {
2222 crate::dsl::const_constraints::ConstConstraint::StrParser(_) => {
2223 format!(" ({message})")
2224 }
2225 _ => String::new(),
2226 };
2227 return Err(AssemblyError::Other(format!(
2228 "strict_values: port '{}' of '{}' ({}) must be {}, but its \
2229 source is the constant {value}{detail}",
2230 sink_port.name,
2231 sink.name,
2232 sink.node.meta().name,
2233 crate::library::assertions::describe_constraint(&constraint),
2234 )));
2235 }
2236 if matches!(proof, ConstraintProof::Unproven) {
2237 let assert_name = format!("__assert_v_{assertion_count}");
2238 assertion_count += 1;
2239 let assert_idx = all_nodes.len();
2240
2241 if let Some(ref mut log) = log {
2242 let from_name = match wire_ref {
2243 WireRef::Input(n) => n.clone(),
2244 WireRef::Node(n, _) => n.clone(),
2245 };
2246 log.push(crate::dsl::events::CompileEvent::AssertionInserted {
2247 from_node: from_name,
2248 to_node: all_nodes[node_idx].name.clone(),
2249 kind: format!("{:?} value-assert {:?}", expected_type, constraint),
2250 });
2251 }
2252
2253 all_name_to_idx.insert(assert_name.clone(), assert_idx);
2254 let assert_wiring = vec![last_source];
2255 while resolved_wiring.len() <= assert_idx {
2256 resolved_wiring.push(Vec::new());
2257 }
2258 resolved_wiring[assert_idx] = assert_wiring;
2259
2260 all_nodes.push(PendingNode {
2261 name: assert_name,
2262 node: crate::library::assertions::assert_value_node(
2263 expected_type,
2264 constraint,
2265 ),
2266 inputs: vec![],
2267 strict_values: None,
2268 });
2269 inserted_guards.insert(assert_idx, constraint);
2270
2271 *node_wiring.last_mut().unwrap() = WireSource::NodeOutput(assert_idx, 0);
2274 } else if let Some(ref mut log) = log {
2275 let from_name = match wire_ref {
2276 WireRef::Input(n) => n.clone(),
2277 WireRef::Node(n, _) => n.clone(),
2278 };
2279 log.push(crate::dsl::events::CompileEvent::AssertionSkipped {
2280 from_node: from_name,
2281 to_node: all_nodes[node_idx].name.clone(),
2282 reason: proof.skip_reason().into(),
2283 });
2284 }
2285 }
2286 }
2287
2288 while resolved_wiring.len() <= node_idx {
2289 resolved_wiring.push(Vec::new());
2290 }
2291 resolved_wiring[node_idx] = node_wiring;
2292 }
2293
2294 while resolved_wiring.len() < all_nodes.len() {
2295 resolved_wiring.push(Vec::new());
2296 }
2297
2298 {
2303 let rules = crate::compile::fusion::default_rules();
2304 if !rules.is_empty() {
2305 let mut output_nodes: Vec<usize> = Vec::new();
2308 for wire_ref in self.outputs.values() {
2309 if let WireRef::Node(node_name, _) = wire_ref
2310 && let Some(&idx) = all_name_to_idx.get(node_name)
2311 {
2312 output_nodes.push(idx);
2313 }
2314 }
2315
2316 let marks: Vec<Option<bool>> =
2320 all_nodes.iter().map(|pn| pn.strict_values).collect();
2321 let mut opt_nodes: Vec<Option<Box<dyn PolydatNode>>> =
2322 all_nodes.into_iter().map(|pn| Some(pn.node)).collect();
2323
2324 let fused_count = crate::compile::fusion::apply_fusions(
2325 &mut opt_nodes,
2326 &mut resolved_wiring,
2327 &mut all_name_to_idx,
2328 &rules,
2329 &output_nodes,
2330 );
2331 if fused_count > 0
2332 && let Some(ref mut log) = log
2333 {
2334 log.push(crate::dsl::events::CompileEvent::FusionApplied {
2335 pattern: "subgraph".into(),
2336 nodes_replaced: fused_count,
2337 });
2338 }
2339
2340 all_nodes = opt_nodes
2343 .into_iter()
2344 .enumerate()
2345 .map(|(i, opt)| PendingNode {
2346 name: all_name_to_idx
2347 .iter()
2348 .find(|&(_, &idx)| idx == i)
2349 .map(|(n, _)| n.clone())
2350 .unwrap_or_else(|| format!("__removed_{i}")),
2351 node: opt.unwrap_or_else(|| {
2352 Box::new(crate::library::identity::Identity::new(
2353 crate::ast::PortType::U64,
2354 ))
2355 }),
2356 inputs: vec![], strict_values: marks[i],
2358 })
2359 .collect();
2360 }
2361 }
2362
2363 let node_count = all_nodes.len();
2370 let mut reachable = vec![false; node_count];
2371 {
2372 let mut worklist: Vec<usize> = Vec::new();
2373 for wire_ref in self.outputs.values() {
2375 if let WireRef::Node(node_name, _) = wire_ref
2376 && let Some(&idx) = all_name_to_idx.get(node_name)
2377 {
2378 worklist.push(idx);
2379 }
2380 }
2381 for (idx, pn) in all_nodes.iter().enumerate() {
2391 if matches!(
2392 pn.node.meta().name.as_str(),
2393 "log_debug" | "log_info" | "log_warn" | "log_error"
2394 ) {
2395 worklist.push(idx);
2396 }
2397 }
2398 while let Some(idx) = worklist.pop() {
2400 if reachable[idx] {
2401 continue;
2402 }
2403 reachable[idx] = true;
2404 for source in &resolved_wiring[idx] {
2405 if let WireSource::NodeOutput(upstream, _) = source
2406 && !reachable[*upstream]
2407 {
2408 worklist.push(*upstream);
2409 }
2410 }
2411 }
2412 }
2413 let live_count = reachable.iter().filter(|&&r| r).count();
2414
2415 if let Some(log) = log.as_deref_mut() {
2420 let mut placed: Vec<(usize, usize, PortType)> = converters
2421 .iter()
2422 .filter(|&(_, &idx)| reachable[idx])
2423 .map(|(&(input, to), &idx)| (idx, input, to))
2424 .collect();
2425 placed.sort_unstable_by_key(|&(idx, _, _)| idx);
2426 for &(idx, input, to) in &placed {
2427 let def = &self.input_defs[input];
2428 let (origin, level) = match (def.converts_to, variance_level) {
2429 (Some(_), Some(level)) => ("inferred", level),
2430 _ => ("declared dyn", crate::dsl::events::EventLevel::Info),
2431 };
2432 log.push(crate::dsl::events::CompileEvent::InputConverterInserted {
2433 input: def.name.clone(),
2434 to: to.to_keyword().to_string(),
2435 node: all_nodes[idx].name.clone(),
2436 origin: origin.to_string(),
2437 level,
2438 });
2439 }
2440 if let Some(level) = variance_level {
2441 for &input in &open {
2442 if placed.iter().any(|&(_, i, _)| i == input) {
2443 continue;
2444 }
2445 let def = &self.input_defs[input];
2446 log.push(crate::dsl::events::CompileEvent::InputConverterInserted {
2447 input: def.name.clone(),
2448 to: def
2449 .converts_to
2450 .unwrap_or(def.port_type)
2451 .to_keyword()
2452 .to_string(),
2453 node: "(none: nothing reads it)".to_string(),
2454 origin: "inferred".to_string(),
2455 level,
2456 });
2457 }
2458 }
2459 }
2460
2461 let mut in_degree = vec![0usize; node_count];
2463 let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); node_count];
2464
2465 for (node_idx, wiring) in resolved_wiring.iter().enumerate() {
2466 if !reachable[node_idx] {
2467 continue;
2468 }
2469 for source in wiring {
2470 if let WireSource::NodeOutput(upstream, _) = source {
2471 in_degree[node_idx] += 1;
2472 dependents[*upstream].push(node_idx);
2473 }
2474 }
2475 }
2476
2477 let mut queue: Vec<usize> = (0..node_count)
2478 .filter(|i| reachable[*i] && in_degree[*i] == 0)
2479 .collect();
2480 let mut sorted_order: Vec<usize> = Vec::with_capacity(live_count);
2481
2482 while let Some(idx) = queue.pop() {
2483 sorted_order.push(idx);
2484 for &dep in &dependents[idx] {
2485 in_degree[dep] -= 1;
2486 if in_degree[dep] == 0 {
2487 queue.push(dep);
2488 }
2489 }
2490 }
2491
2492 if sorted_order.len() != live_count {
2493 return Err(AssemblyError::CycleDetected);
2494 }
2495
2496 let mut old_to_new = vec![0usize; node_count];
2497 for (new_idx, &old_idx) in sorted_order.iter().enumerate() {
2498 old_to_new[old_idx] = new_idx;
2499 }
2500
2501 let final_marks: Vec<Option<bool>> = sorted_order
2503 .iter()
2504 .map(|&old_idx| all_nodes[old_idx].strict_values)
2505 .collect();
2506 let mut sorted_nodes: Vec<Option<Box<dyn PolydatNode>>> =
2507 all_nodes.into_iter().map(|pn| Some(pn.node)).collect();
2508
2509 let final_nodes: Vec<Box<dyn PolydatNode>> = sorted_order
2510 .iter()
2511 .map(|&old_idx| sorted_nodes[old_idx].take().unwrap())
2512 .collect();
2513
2514 let final_wiring: Vec<Vec<WireSource>> = sorted_order
2515 .iter()
2516 .map(|&old_idx| {
2517 resolved_wiring[old_idx]
2518 .iter()
2519 .map(|source| match source {
2520 WireSource::Input(c) => WireSource::Input(*c),
2521 WireSource::NodeOutput(old_up, port) => {
2522 WireSource::NodeOutput(old_to_new[*old_up], *port)
2523 }
2524 })
2525 .collect()
2526 })
2527 .collect();
2528
2529 let mut final_output_map: HashMap<String, (usize, usize)> = HashMap::new();
2530 for (name, wire_ref) in &self.outputs {
2531 match wire_ref {
2532 WireRef::Input(coord_name) => {
2533 return Err(AssemblyError::UnknownWire(format!(
2534 "output '{name}' references coordinate '{coord_name}' directly; \
2535 wire through a node instead"
2536 )));
2537 }
2538 WireRef::Node(node_name, port) => {
2539 let old_idx = all_name_to_idx
2540 .get(node_name)
2541 .ok_or_else(|| AssemblyError::UnknownWire(node_name.clone()))?;
2542 final_output_map.insert(name.clone(), (old_to_new[*old_idx], *port));
2543 }
2544 }
2545 }
2546
2547 for (i, f) in crate::compile::roundtrip_lint::lint_type_round_trips(
2555 &final_nodes,
2556 &final_wiring,
2557 &self.input_defs,
2558 ) {
2559 if final_marks[i].unwrap_or(strict_values) {
2560 return Err(AssemblyError::Other(f.message()));
2561 }
2562 crate::library::support::audit::warn(&f.message());
2565 if let Some(ref mut log) = log {
2566 log.push(crate::dsl::events::CompileEvent::Warning {
2567 message: f.message(),
2568 });
2569 }
2570 }
2571
2572 if let Some(log) = log {
2573 let resolved_view = ResolvedDag {
2574 nodes: final_nodes,
2575 wiring: final_wiring,
2576 input_defs: self.input_defs,
2577 coord_count: self.coord_count,
2578 output_map: final_output_map,
2579 output_order: self.output_order,
2580 source: self.source,
2581 context: self.context,
2582 output_modifiers: self.output_modifiers,
2583 const_outputs: self.const_outputs,
2584 const_inits: self.const_inits,
2585 cursor_schemas: self.cursor_schemas,
2586 ledger: self.ledger,
2587 resources: self.resources,
2588 };
2589 Self::log_forms(&resolved_view, log);
2590 return Ok(resolved_view);
2591 }
2592 Ok(ResolvedDag {
2593 nodes: final_nodes,
2594 wiring: final_wiring,
2595 input_defs: self.input_defs,
2596 coord_count: self.coord_count,
2597 output_map: final_output_map,
2598 output_order: self.output_order,
2599 source: self.source,
2600 context: self.context,
2601 output_modifiers: self.output_modifiers,
2602 const_outputs: self.const_outputs,
2603 const_inits: self.const_inits,
2604 cursor_schemas: self.cursor_schemas,
2605 ledger: self.ledger,
2606 resources: self.resources,
2607 })
2608 }
2609}
2610
2611enum ConstraintProof {
2614 NotChecked,
2616 ConstantSatisfies,
2619 ConstantNone,
2622 Guarded,
2625 Violated {
2628 value: String,
2630 message: String,
2632 },
2633 Unproven,
2635}
2636
2637impl ConstraintProof {
2638 fn skip_reason(&self) -> &'static str {
2641 match self {
2642 ConstraintProof::NotChecked => "strict_values not enabled",
2643 ConstraintProof::ConstantSatisfies => "constant source satisfies the constraint",
2644 ConstraintProof::ConstantNone => "constant source is None",
2645 ConstraintProof::Guarded => "upstream assertion",
2646 ConstraintProof::Violated { .. } => "constant source fails the constraint",
2647 ConstraintProof::Unproven => "no skip rule matched",
2648 }
2649 }
2650}
2651
2652fn constraint_proof(
2666 all_nodes: &[PendingNode],
2667 inserted_guards: &HashMap<usize, crate::dsl::const_constraints::ConstConstraint>,
2668 volatile_nodes: &std::collections::HashSet<String>,
2669 src: &WireSource,
2670 constraint: &crate::dsl::const_constraints::ConstConstraint,
2671) -> ConstraintProof {
2672 let WireSource::NodeOutput(idx, port) = src else {
2673 return ConstraintProof::Unproven;
2674 };
2675 if let Some(guarded) = inserted_guards.get(idx) {
2676 return if crate::library::assertions::same_constraint(guarded, constraint) {
2677 ConstraintProof::Guarded
2678 } else {
2679 ConstraintProof::Unproven
2680 };
2681 }
2682 let pending = &all_nodes[*idx];
2683 let node = pending.node.as_ref();
2684 let nondeterministic = matches!(node.purity(), crate::ast::Purity::Nondeterministic { .. })
2685 || volatile_nodes.contains(&pending.name);
2686 if !node.meta().wire_inputs().is_empty() || nondeterministic {
2687 return ConstraintProof::Unproven;
2688 }
2689 let mut outputs = vec![crate::ast::Value::None; node.meta().outs.len()];
2692 let evaluated = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2693 node.eval(&[], &mut outputs);
2694 }));
2695 if evaluated.is_err() {
2696 return ConstraintProof::Unproven;
2697 }
2698 let Some(value) = outputs.get(*port) else {
2699 return ConstraintProof::Unproven;
2700 };
2701 if matches!(value, crate::ast::Value::None) {
2702 return ConstraintProof::ConstantNone;
2703 }
2704 match crate::library::assertions::check_value(constraint, value, "the value") {
2705 Some(Ok(())) => ConstraintProof::ConstantSatisfies,
2706 Some(Err(message)) => ConstraintProof::Violated {
2707 value: value.to_display_string(),
2708 message,
2709 },
2710 None => ConstraintProof::Unproven,
2711 }
2712}
2713
2714pub(crate) fn shared_outputs_of(resolved: &ResolvedDag) -> Vec<&str> {
2717 let mut shared: Vec<&str> = resolved
2718 .output_modifiers
2719 .iter()
2720 .filter(|(_, m)| **m == crate::dsl::ast::BindingModifier::SHARED)
2721 .map(|(name, _)| name.as_str())
2722 .collect();
2723 shared.sort();
2724 shared
2725}
2726
2727fn is_lossless_widening(from: PortType, to: PortType) -> bool {
2731 use PortType as P;
2732 matches!(
2733 (from, to),
2734 (P::U64, P::F64)
2735 | (P::U32, P::U64)
2736 | (P::U32, P::I64)
2737 | (P::U32, P::F64)
2738 | (P::I32, P::I64)
2739 | (P::I32, P::F64)
2740 | (P::I64, P::F64)
2741 | (P::F32, P::F64)
2742 )
2743}
2744
2745pub(crate) fn wire_types_of(resolved: &ResolvedDag, node_idx: usize) -> Vec<PortType> {
2746 resolved.wiring[node_idx]
2747 .iter()
2748 .map(|src| match src {
2749 crate::kernel::WireSource::Input(i) => resolved.input_defs[*i].port_type,
2750 crate::kernel::WireSource::NodeOutput(j, p) => resolved.nodes[*j].meta().outs[*p].typ,
2751 })
2752 .collect()
2753}
2754
2755pub fn auto_adapter(from: PortType, to: PortType) -> Option<Box<dyn PolydatNode>> {
2759 use crate::library::convert::{
2760 BoolToStr, BoolToU64, F32ToF64, F32ToString, I32ToF64, I32ToI64, I32ToString, I64ToF64,
2761 I64ToString, U32ToF64, U32ToI64, U32ToString, U32ToU64,
2762 };
2763 use crate::library::polyfill as P;
2764 use crate::library::polyfill_128 as W;
2765 use crate::library::polyfill_complete as C;
2766 use crate::library::polyfill_narrow as N;
2767 match (from, to) {
2768 (PortType::U64, PortType::F64) => Some(Box::new(U64ToF64::new())),
2770 (PortType::U32, PortType::U64) => Some(Box::new(U32ToU64::new())),
2771 (PortType::U32, PortType::I64) => Some(Box::new(U32ToI64::new())),
2772 (PortType::U32, PortType::F64) => Some(Box::new(U32ToF64::new())),
2773 (PortType::I32, PortType::I64) => Some(Box::new(I32ToI64::new())),
2774 (PortType::I32, PortType::F64) => Some(Box::new(I32ToF64::new())),
2775 (PortType::I32, PortType::F32) => Some(Box::new(P::I32ToF32::new())),
2779 (PortType::I64, PortType::F64) => Some(Box::new(I64ToF64::new())),
2780 (PortType::F32, PortType::F64) => Some(Box::new(F32ToF64::new())),
2781
2782 (PortType::U64, PortType::Str) => Some(Box::new(U64ToString::new())),
2784 (PortType::F64, PortType::Str) => Some(Box::new(F64ToString::new())),
2785 (PortType::Bool, PortType::Str) => Some(Box::new(BoolToStr::new())),
2786 (PortType::Json, PortType::Str) => Some(Box::new(JsonToStr::new())),
2787 (PortType::U32, PortType::Str) => Some(Box::new(U32ToString::new())),
2788 (PortType::I32, PortType::Str) => Some(Box::new(I32ToString::new())),
2789 (PortType::I64, PortType::Str) => Some(Box::new(I64ToString::new())),
2790 (PortType::F32, PortType::Str) => Some(Box::new(F32ToString::new())),
2791
2792 (PortType::Bool, PortType::U64) => Some(Box::new(BoolToU64::new())),
2794 (PortType::Bool, PortType::U32) => Some(Box::new(P::BoolToU32::new())),
2795 (PortType::Bool, PortType::I64) => Some(Box::new(P::BoolToI64::new())),
2796 (PortType::Bool, PortType::I32) => Some(Box::new(P::BoolToI32::new())),
2797 (PortType::Bool, PortType::F64) => Some(Box::new(P::BoolToF64::new())),
2798 (PortType::Bool, PortType::F32) => Some(Box::new(P::BoolToF32::new())),
2799 (PortType::U64, PortType::Bool) => {
2800 Some(Box::new(crate::library::convert::U64ToBool::new()))
2801 }
2802 (PortType::U32, PortType::Bool) => Some(Box::new(P::U32ToBool::new())),
2803 (PortType::I64, PortType::Bool) => Some(Box::new(P::I64ToBool::new())),
2804 (PortType::I32, PortType::Bool) => Some(Box::new(P::I32ToBool::new())),
2805 (PortType::F64, PortType::Bool) => Some(Box::new(P::F64ToBool::new())),
2806 (PortType::F32, PortType::Bool) => Some(Box::new(P::F32ToBool::new())),
2807
2808 (PortType::U64, PortType::Bytes) => Some(Box::new(P::U64ToBytes::new())),
2810 (PortType::U32, PortType::Bytes) => Some(Box::new(P::U32ToBytes::new())),
2811 (PortType::I64, PortType::Bytes) => Some(Box::new(P::I64ToBytes::new())),
2812 (PortType::I32, PortType::Bytes) => Some(Box::new(P::I32ToBytes::new())),
2813 (PortType::F64, PortType::Bytes) => Some(Box::new(P::F64ToBytes::new())),
2814 (PortType::F32, PortType::Bytes) => Some(Box::new(P::F32ToBytes::new())),
2815 (PortType::Bool, PortType::Bytes) => Some(Box::new(P::BoolToBytes::new())),
2816 (PortType::VecF32, PortType::Bytes) => Some(Box::new(P::VecF32ToBytes::new())),
2817 (PortType::VecI32, PortType::Bytes) => Some(Box::new(P::VecI32ToBytes::new())),
2818
2819 (PortType::U64, PortType::Json) => Some(Box::new(P::U64ToJson::new())),
2823 (PortType::U32, PortType::Json) => Some(Box::new(P::U32ToJson::new())),
2824 (PortType::I64, PortType::Json) => Some(Box::new(P::I64ToJson::new())),
2825 (PortType::I32, PortType::Json) => Some(Box::new(P::I32ToJson::new())),
2826 (PortType::Bool, PortType::Json) => Some(Box::new(P::BoolToJson::new())),
2827 (PortType::VecI32, PortType::Json) => Some(Box::new(P::VecI32ToJson::new())),
2828
2829 (PortType::VecI32, PortType::VecF32) => Some(Box::new(P::VecI32ToVecF32::new())),
2831
2832 (PortType::U8, PortType::U64) => Some(Box::new(N::U8ToU64::new())),
2837 (PortType::U8, PortType::U32) => Some(Box::new(N::U8ToU32::new())),
2838 (PortType::U8, PortType::U16) => Some(Box::new(N::U8ToU16::new())),
2839 (PortType::U8, PortType::F64) => Some(Box::new(N::U8ToF64::new())),
2840 (PortType::U16, PortType::U64) => Some(Box::new(N::U16ToU64::new())),
2841 (PortType::U16, PortType::U32) => Some(Box::new(N::U16ToU32::new())),
2842 (PortType::U16, PortType::F64) => Some(Box::new(N::U16ToF64::new())),
2843 (PortType::I8, PortType::I64) => Some(Box::new(N::I8ToI64::new())),
2844 (PortType::I8, PortType::I32) => Some(Box::new(N::I8ToI32::new())),
2845 (PortType::I8, PortType::I16) => Some(Box::new(N::I8ToI16::new())),
2846 (PortType::I8, PortType::F64) => Some(Box::new(N::I8ToF64::new())),
2847 (PortType::I16, PortType::I64) => Some(Box::new(N::I16ToI64::new())),
2848 (PortType::I16, PortType::I32) => Some(Box::new(N::I16ToI32::new())),
2849 (PortType::I16, PortType::F64) => Some(Box::new(N::I16ToF64::new())),
2850 (PortType::F16, PortType::F32) => Some(Box::new(N::F16ToF32::new())),
2851 (PortType::F16, PortType::F64) => Some(Box::new(N::F16ToF64::new())),
2852 (PortType::U8, PortType::I16) => Some(Box::new(N::U8ToI16::new())),
2855 (PortType::U8, PortType::I32) => Some(Box::new(N::U8ToI32::new())),
2856 (PortType::U8, PortType::I64) => Some(Box::new(N::U8ToI64::new())),
2857 (PortType::U8, PortType::F32) => Some(Box::new(N::U8ToF32::new())),
2858 (PortType::U16, PortType::I32) => Some(Box::new(N::U16ToI32::new())),
2859 (PortType::U16, PortType::I64) => Some(Box::new(N::U16ToI64::new())),
2860 (PortType::U16, PortType::F32) => Some(Box::new(N::U16ToF32::new())),
2861 (PortType::I8, PortType::F32) => Some(Box::new(N::I8ToF32::new())),
2862 (PortType::I16, PortType::F32) => Some(Box::new(N::I16ToF32::new())),
2863 (PortType::U8, PortType::F16) => Some(Box::new(N::U8ToF16::new())),
2864 (PortType::I8, PortType::F16) => Some(Box::new(N::I8ToF16::new())),
2865 (PortType::U8, PortType::Str) => Some(Box::new(N::U8ToString::new())),
2866 (PortType::U16, PortType::Str) => Some(Box::new(N::U16ToString::new())),
2867 (PortType::I8, PortType::Str) => Some(Box::new(N::I8ToString::new())),
2868 (PortType::I16, PortType::Str) => Some(Box::new(N::I16ToString::new())),
2869 (PortType::F16, PortType::Str) => Some(Box::new(N::F16ToString::new())),
2870 (PortType::Bool, PortType::U8) => Some(Box::new(N::BoolToU8::new())),
2871 (PortType::Bool, PortType::U16) => Some(Box::new(N::BoolToU16::new())),
2872 (PortType::Bool, PortType::I8) => Some(Box::new(N::BoolToI8::new())),
2873 (PortType::Bool, PortType::I16) => Some(Box::new(N::BoolToI16::new())),
2874 (PortType::Bool, PortType::F16) => Some(Box::new(N::BoolToF16::new())),
2875 (PortType::U8, PortType::Bool) => Some(Box::new(N::U8ToBool::new())),
2876 (PortType::U16, PortType::Bool) => Some(Box::new(N::U16ToBool::new())),
2877 (PortType::I8, PortType::Bool) => Some(Box::new(N::I8ToBool::new())),
2878 (PortType::I16, PortType::Bool) => Some(Box::new(N::I16ToBool::new())),
2879 (PortType::F16, PortType::Bool) => Some(Box::new(N::F16ToBool::new())),
2880 (PortType::U8, PortType::Bytes) => Some(Box::new(N::U8ToBytes::new())),
2881 (PortType::U16, PortType::Bytes) => Some(Box::new(N::U16ToBytes::new())),
2882 (PortType::I8, PortType::Bytes) => Some(Box::new(N::I8ToBytes::new())),
2883 (PortType::I16, PortType::Bytes) => Some(Box::new(N::I16ToBytes::new())),
2884 (PortType::F16, PortType::Bytes) => Some(Box::new(N::F16ToBytes::new())),
2885 (PortType::U8, PortType::Json) => Some(Box::new(N::U8ToJson::new())),
2886 (PortType::U16, PortType::Json) => Some(Box::new(N::U16ToJson::new())),
2887 (PortType::I8, PortType::Json) => Some(Box::new(N::I8ToJson::new())),
2888 (PortType::I16, PortType::Json) => Some(Box::new(N::I16ToJson::new())),
2889
2890 (PortType::U64, PortType::U128) => Some(Box::new(W::U64ToU128::new())),
2895 (PortType::U64, PortType::I128) => Some(Box::new(W::U64ToI128::new())),
2896 (PortType::I64, PortType::I128) => Some(Box::new(W::I64ToI128::new())),
2897 (PortType::U8, PortType::U128) => Some(Box::new(W::U8ToU128::new())),
2902 (PortType::U8, PortType::I128) => Some(Box::new(W::U8ToI128::new())),
2903 (PortType::U16, PortType::U128) => Some(Box::new(W::U16ToU128::new())),
2904 (PortType::U16, PortType::I128) => Some(Box::new(W::U16ToI128::new())),
2905 (PortType::U32, PortType::U128) => Some(Box::new(W::U32ToU128::new())),
2906 (PortType::U32, PortType::I128) => Some(Box::new(W::U32ToI128::new())),
2907 (PortType::I8, PortType::I128) => Some(Box::new(W::I8ToI128::new())),
2908 (PortType::I16, PortType::I128) => Some(Box::new(W::I16ToI128::new())),
2909 (PortType::I32, PortType::I128) => Some(Box::new(W::I32ToI128::new())),
2910 (PortType::Bool, PortType::U128) => Some(Box::new(W::BoolToU128::new())),
2911 (PortType::Bool, PortType::I128) => Some(Box::new(W::BoolToI128::new())),
2912 (PortType::U128, PortType::Bool) => Some(Box::new(W::U128ToBool::new())),
2913 (PortType::I128, PortType::Bool) => Some(Box::new(W::I128ToBool::new())),
2914 (PortType::U128, PortType::F64) => Some(Box::new(W::U128ToF64::new())),
2915 (PortType::I128, PortType::F64) => Some(Box::new(W::I128ToF64::new())),
2916 (PortType::U128, PortType::Str) => Some(Box::new(W::U128ToString::new())),
2917 (PortType::I128, PortType::Str) => Some(Box::new(W::I128ToString::new())),
2918 (PortType::U128, PortType::Bytes) => Some(Box::new(W::U128ToBytes::new())),
2919 (PortType::I128, PortType::Bytes) => Some(Box::new(W::I128ToBytes::new())),
2920 (PortType::U128, PortType::Json) => Some(Box::new(W::U128ToJson::new())),
2921 (PortType::I128, PortType::Json) => Some(Box::new(W::I128ToJson::new())),
2922
2923 (from, to) if crate::library::register_view::is_reg_port(from) => {
2928 crate::library::register_view::reg_view(to)
2929 }
2930
2931 (PortType::VecI8, PortType::VecI16) => Some(Box::new(C::VecI8ToVecI16::new())),
2935 (PortType::VecI8, PortType::VecI32) => Some(Box::new(C::VecI8ToVecI32::new())),
2936 (PortType::VecI8, PortType::VecI64) => Some(Box::new(C::VecI8ToVecI64::new())),
2937 (PortType::VecI8, PortType::VecF16) => Some(Box::new(C::VecI8ToVecF16::new())),
2938 (PortType::VecI8, PortType::VecF32) => Some(Box::new(C::VecI8ToVecF32::new())),
2939 (PortType::VecI8, PortType::VecF64) => Some(Box::new(C::VecI8ToVecF64::new())),
2940 (PortType::VecI16, PortType::VecI32) => Some(Box::new(C::VecI16ToVecI32::new())),
2941 (PortType::VecI16, PortType::VecI64) => Some(Box::new(C::VecI16ToVecI64::new())),
2942 (PortType::VecI16, PortType::VecF32) => Some(Box::new(C::VecI16ToVecF32::new())),
2943 (PortType::VecI16, PortType::VecF64) => Some(Box::new(C::VecI16ToVecF64::new())),
2944 (PortType::VecI32, PortType::VecI64) => Some(Box::new(C::VecI32ToVecI64::new())),
2945 (PortType::VecI32, PortType::VecF64) => Some(Box::new(C::VecI32ToVecF64::new())),
2946 (PortType::VecI64, PortType::VecF64) => Some(Box::new(C::VecI64ToVecF64::new())),
2947 (PortType::VecF16, PortType::VecF32) => Some(Box::new(C::VecF16ToVecF32::new())),
2948 (PortType::VecF16, PortType::VecF64) => Some(Box::new(C::VecF16ToVecF64::new())),
2949 (PortType::VecF32, PortType::VecF64) => Some(Box::new(C::VecF32ToVecF64::new())),
2950 (PortType::VecF64, PortType::Bytes) => Some(Box::new(C::VecF64ToBytes::new())),
2951 (PortType::VecI64, PortType::Bytes) => Some(Box::new(C::VecI64ToBytes::new())),
2952 (PortType::VecF16, PortType::Bytes) => Some(Box::new(C::VecF16ToBytes::new())),
2953 (PortType::VecI16, PortType::Bytes) => Some(Box::new(C::VecI16ToBytes::new())),
2954 (PortType::VecI8, PortType::Bytes) => Some(Box::new(C::VecI8ToBytes::new())),
2955 (PortType::VecI64, PortType::Json) => Some(Box::new(C::VecI64ToJson::new())),
2956 (PortType::VecI16, PortType::Json) => Some(Box::new(C::VecI16ToJson::new())),
2957 (PortType::VecI8, PortType::Json) => Some(Box::new(C::VecI8ToJson::new())),
2958 (PortType::VecI32, PortType::Str) => Some(Box::new(P::VecI32ToStr::new())),
2959 (PortType::VecI64, PortType::Str) => Some(Box::new(C::VecI64ToStr::new())),
2960 (PortType::VecI16, PortType::Str) => Some(Box::new(C::VecI16ToStr::new())),
2961 (PortType::VecI8, PortType::Str) => Some(Box::new(C::VecI8ToStr::new())),
2962
2963 _ => None,
2964 }
2965}
2966
2967pub fn boundary_adapter(from: PortType, to: PortType) -> Option<Box<dyn PolydatNode>> {
2997 if let Some(adapter) = auto_adapter(from, to) {
2998 return Some(adapter);
2999 }
3000 use crate::library::convert::{StrToBool, StrToF64, StrToU64};
3001 use crate::library::polyfill as P;
3002 use crate::library::polyfill_128 as W;
3003 use crate::library::polyfill_complete as C;
3004 use crate::library::polyfill_narrow as N;
3005 match (from, to) {
3006 (PortType::U64, PortType::U32) => Some(Box::new(P::U64ToU32::new())),
3008 (PortType::U64, PortType::I64) => Some(Box::new(P::U64ToI64::new())),
3009 (PortType::U64, PortType::I32) => Some(Box::new(P::U64ToI32::new())),
3010 (PortType::U64, PortType::F32) => Some(Box::new(P::U64ToF32::new())),
3011 (PortType::U32, PortType::I32) => Some(Box::new(P::U32ToI32::new())),
3012 (PortType::U32, PortType::F32) => Some(Box::new(P::U32ToF32::new())),
3013 (PortType::I64, PortType::U64) => Some(Box::new(P::I64ToU64::new())),
3014 (PortType::I64, PortType::U32) => Some(Box::new(P::I64ToU32::new())),
3015 (PortType::I64, PortType::I32) => Some(Box::new(P::I64ToI32::new())),
3016 (PortType::I64, PortType::F32) => Some(Box::new(P::I64ToF32::new())),
3017 (PortType::I32, PortType::U64) => Some(Box::new(P::I32ToU64::new())),
3018 (PortType::I32, PortType::U32) => Some(Box::new(P::I32ToU32::new())),
3019 (PortType::I32, PortType::F32) => Some(Box::new(P::I32ToF32::new())),
3020 (PortType::F64, PortType::U64) => Some(Box::new(P::F64ToU64Checked::new())),
3021 (PortType::F64, PortType::U32) => Some(Box::new(P::F64ToU32::new())),
3022 (PortType::F64, PortType::I64) => Some(Box::new(P::F64ToI64::new())),
3023 (PortType::F64, PortType::I32) => Some(Box::new(P::F64ToI32::new())),
3024 (PortType::F64, PortType::F32) => Some(Box::new(P::F64ToF32::new())),
3025 (PortType::F32, PortType::U64) => Some(Box::new(P::F32ToU64::new())),
3026 (PortType::F32, PortType::U32) => Some(Box::new(P::F32ToU32::new())),
3027 (PortType::F32, PortType::I64) => Some(Box::new(P::F32ToI64::new())),
3028 (PortType::F32, PortType::I32) => Some(Box::new(P::F32ToI32::new())),
3029
3030 (PortType::Str, PortType::Bool) => Some(Box::new(StrToBool::new())),
3032 (PortType::Str, PortType::U64) => Some(Box::new(StrToU64::new())),
3033 (PortType::Str, PortType::F64) => Some(Box::new(StrToF64::new())),
3034 (PortType::Str, PortType::U32) => Some(Box::new(P::StrToU32::new())),
3035 (PortType::Str, PortType::I64) => Some(Box::new(P::StrToI64::new())),
3036 (PortType::Str, PortType::I32) => Some(Box::new(P::StrToI32::new())),
3037 (PortType::Str, PortType::F32) => Some(Box::new(P::StrToF32::new())),
3038 (PortType::Str, PortType::Bytes) => Some(Box::new(P::StrToBytes::new())),
3039 (PortType::Str, PortType::Json) => Some(Box::new(P::StrToJson::new())),
3040 (PortType::Str, PortType::VecF32) => Some(Box::new(P::StrToVecF32::new())),
3041 (PortType::Str, PortType::VecI32) => Some(Box::new(P::StrToVecI32::new())),
3042
3043 (PortType::Bytes, PortType::U64) => Some(Box::new(P::BytesToU64::new())),
3045 (PortType::Bytes, PortType::U32) => Some(Box::new(P::BytesToU32::new())),
3046 (PortType::Bytes, PortType::I64) => Some(Box::new(P::BytesToI64::new())),
3047 (PortType::Bytes, PortType::I32) => Some(Box::new(P::BytesToI32::new())),
3048 (PortType::Bytes, PortType::F64) => Some(Box::new(P::BytesToF64::new())),
3049 (PortType::Bytes, PortType::F32) => Some(Box::new(P::BytesToF32::new())),
3050 (PortType::Bytes, PortType::Bool) => Some(Box::new(P::BytesToBool::new())),
3051 (PortType::Bytes, PortType::Str) => Some(Box::new(P::BytesToStr::new())),
3052 (PortType::Bytes, PortType::Json) => Some(Box::new(P::BytesToJson::new())),
3053 (PortType::Bytes, PortType::VecF32) => Some(Box::new(P::BytesToVecF32::new())),
3054 (PortType::Bytes, PortType::VecI32) => Some(Box::new(P::BytesToVecI32::new())),
3055
3056 (PortType::Json, PortType::U64) => Some(Box::new(P::JsonToU64::new())),
3058 (PortType::Json, PortType::U32) => Some(Box::new(P::JsonToU32::new())),
3059 (PortType::Json, PortType::I64) => Some(Box::new(P::JsonToI64::new())),
3060 (PortType::Json, PortType::I32) => Some(Box::new(P::JsonToI32::new())),
3061 (PortType::Json, PortType::F64) => Some(Box::new(P::JsonToF64::new())),
3062 (PortType::Json, PortType::F32) => Some(Box::new(P::JsonToF32::new())),
3063 (PortType::Json, PortType::Bool) => Some(Box::new(P::JsonToBool::new())),
3064 (PortType::Json, PortType::Bytes) => Some(Box::new(P::JsonToBytes::new())),
3065 (PortType::Json, PortType::VecF32) => Some(Box::new(P::JsonToVecF32::new())),
3066 (PortType::Json, PortType::VecI32) => Some(Box::new(P::JsonToVecI32::new())),
3067
3068 (PortType::F64, PortType::Json) => Some(Box::new(P::F64ToJson::new())),
3070 (PortType::F32, PortType::Json) => Some(Box::new(P::F32ToJson::new())),
3071 (PortType::VecF32, PortType::Json) => Some(Box::new(P::VecF32ToJson::new())),
3072 (PortType::VecF32, PortType::Str) => Some(Box::new(P::VecF32ToStr::new())),
3073
3074 (PortType::VecF32, PortType::VecI32) => Some(Box::new(P::VecF32ToVecI32::new())),
3076
3077 (PortType::U64, PortType::U8) => Some(Box::new(N::U64ToU8::new())),
3081 (PortType::U32, PortType::U8) => Some(Box::new(N::U32ToU8::new())),
3082 (PortType::U16, PortType::U8) => Some(Box::new(N::U16ToU8::new())),
3083 (PortType::I64, PortType::U8) => Some(Box::new(N::I64ToU8::new())),
3084 (PortType::F64, PortType::U8) => Some(Box::new(N::F64ToU8::new())),
3085 (PortType::U64, PortType::U16) => Some(Box::new(N::U64ToU16::new())),
3086 (PortType::U32, PortType::U16) => Some(Box::new(N::U32ToU16::new())),
3087 (PortType::I64, PortType::U16) => Some(Box::new(N::I64ToU16::new())),
3088 (PortType::F64, PortType::U16) => Some(Box::new(N::F64ToU16::new())),
3089 (PortType::I64, PortType::I8) => Some(Box::new(N::I64ToI8::new())),
3090 (PortType::I32, PortType::I8) => Some(Box::new(N::I32ToI8::new())),
3091 (PortType::U64, PortType::I8) => Some(Box::new(N::U64ToI8::new())),
3092 (PortType::F64, PortType::I8) => Some(Box::new(N::F64ToI8::new())),
3093 (PortType::I64, PortType::I16) => Some(Box::new(N::I64ToI16::new())),
3094 (PortType::I32, PortType::I16) => Some(Box::new(N::I32ToI16::new())),
3095 (PortType::U64, PortType::I16) => Some(Box::new(N::U64ToI16::new())),
3096 (PortType::F64, PortType::I16) => Some(Box::new(N::F64ToI16::new())),
3097 (PortType::F64, PortType::F16) => Some(Box::new(N::F64ToF16::new())),
3098 (PortType::F32, PortType::F16) => Some(Box::new(N::F32ToF16::new())),
3099 (PortType::U64, PortType::F16) => Some(Box::new(N::U64ToF16::new())),
3100 (PortType::Str, PortType::U8) => Some(Box::new(N::StrToU8::new())),
3101 (PortType::Str, PortType::U16) => Some(Box::new(N::StrToU16::new())),
3102 (PortType::Str, PortType::I8) => Some(Box::new(N::StrToI8::new())),
3103 (PortType::Str, PortType::I16) => Some(Box::new(N::StrToI16::new())),
3104 (PortType::Str, PortType::F16) => Some(Box::new(N::StrToF16::new())),
3105 (PortType::Bytes, PortType::U8) => Some(Box::new(N::BytesToU8::new())),
3106 (PortType::Bytes, PortType::U16) => Some(Box::new(N::BytesToU16::new())),
3107 (PortType::Bytes, PortType::I8) => Some(Box::new(N::BytesToI8::new())),
3108 (PortType::Bytes, PortType::I16) => Some(Box::new(N::BytesToI16::new())),
3109 (PortType::Bytes, PortType::F16) => Some(Box::new(N::BytesToF16::new())),
3110 (PortType::Json, PortType::U8) => Some(Box::new(N::JsonToU8::new())),
3111 (PortType::Json, PortType::U16) => Some(Box::new(N::JsonToU16::new())),
3112 (PortType::Json, PortType::I8) => Some(Box::new(N::JsonToI8::new())),
3113 (PortType::Json, PortType::I16) => Some(Box::new(N::JsonToI16::new())),
3114 (PortType::Json, PortType::F16) => Some(Box::new(N::JsonToF16::new())),
3115 (PortType::F16, PortType::Json) => Some(Box::new(N::F16ToJson::new())),
3117
3118 (PortType::U128, PortType::U64) => Some(Box::new(W::U128ToU64::new())),
3120 (PortType::I128, PortType::I64) => Some(Box::new(W::I128ToI64::new())),
3121 (PortType::I64, PortType::U128) => Some(Box::new(W::I64ToU128::new())),
3122 (PortType::U128, PortType::I128) => Some(Box::new(W::U128ToI128::new())),
3123 (PortType::I128, PortType::U128) => Some(Box::new(W::I128ToU128::new())),
3124 (PortType::F64, PortType::U128) => Some(Box::new(W::F64ToU128::new())),
3125 (PortType::F64, PortType::I128) => Some(Box::new(W::F64ToI128::new())),
3126 (PortType::Str, PortType::U128) => Some(Box::new(W::StrToU128::new())),
3127 (PortType::Str, PortType::I128) => Some(Box::new(W::StrToI128::new())),
3128 (PortType::Bytes, PortType::U128) => Some(Box::new(W::BytesToU128::new())),
3129 (PortType::Bytes, PortType::I128) => Some(Box::new(W::BytesToI128::new())),
3130 (PortType::Json, PortType::U128) => Some(Box::new(W::JsonToU128::new())),
3131 (PortType::Json, PortType::I128) => Some(Box::new(W::JsonToI128::new())),
3132
3133 (PortType::U8, PortType::I8) => Some(Box::new(C::U8ToI8::new())),
3138 (PortType::I8, PortType::U8) => Some(Box::new(C::I8ToU8::new())),
3139 (PortType::I8, PortType::U16) => Some(Box::new(C::I8ToU16::new())),
3140 (PortType::I8, PortType::U32) => Some(Box::new(C::I8ToU32::new())),
3141 (PortType::I8, PortType::U64) => Some(Box::new(C::I8ToU64::new())),
3142 (PortType::I8, PortType::U128) => Some(Box::new(C::I8ToU128::new())),
3143 (PortType::U16, PortType::I8) => Some(Box::new(C::U16ToI8::new())),
3144 (PortType::U16, PortType::I16) => Some(Box::new(C::U16ToI16::new())),
3145 (PortType::U16, PortType::F16) => Some(Box::new(C::U16ToF16::new())),
3146 (PortType::I16, PortType::U8) => Some(Box::new(C::I16ToU8::new())),
3147 (PortType::I16, PortType::I8) => Some(Box::new(C::I16ToI8::new())),
3148 (PortType::I16, PortType::U16) => Some(Box::new(C::I16ToU16::new())),
3149 (PortType::I16, PortType::F16) => Some(Box::new(C::I16ToF16::new())),
3150 (PortType::I16, PortType::U32) => Some(Box::new(C::I16ToU32::new())),
3151 (PortType::I16, PortType::U64) => Some(Box::new(C::I16ToU64::new())),
3152 (PortType::I16, PortType::U128) => Some(Box::new(C::I16ToU128::new())),
3153 (PortType::U32, PortType::I8) => Some(Box::new(C::U32ToI8::new())),
3154 (PortType::U32, PortType::I16) => Some(Box::new(C::U32ToI16::new())),
3155 (PortType::U32, PortType::F16) => Some(Box::new(C::U32ToF16::new())),
3156 (PortType::I32, PortType::U8) => Some(Box::new(C::I32ToU8::new())),
3157 (PortType::I32, PortType::U16) => Some(Box::new(C::I32ToU16::new())),
3158 (PortType::I32, PortType::F16) => Some(Box::new(C::I32ToF16::new())),
3159 (PortType::I32, PortType::U128) => Some(Box::new(C::I32ToU128::new())),
3160 (PortType::F16, PortType::U8) => Some(Box::new(C::F16ToU8::new())),
3161 (PortType::F16, PortType::I8) => Some(Box::new(C::F16ToI8::new())),
3162 (PortType::F16, PortType::U16) => Some(Box::new(C::F16ToU16::new())),
3163 (PortType::F16, PortType::I16) => Some(Box::new(C::F16ToI16::new())),
3164 (PortType::F16, PortType::U32) => Some(Box::new(C::F16ToU32::new())),
3165 (PortType::F16, PortType::I32) => Some(Box::new(C::F16ToI32::new())),
3166 (PortType::F16, PortType::U64) => Some(Box::new(C::F16ToU64::new())),
3167 (PortType::F16, PortType::I64) => Some(Box::new(C::F16ToI64::new())),
3168 (PortType::F16, PortType::U128) => Some(Box::new(C::F16ToU128::new())),
3169 (PortType::F16, PortType::I128) => Some(Box::new(C::F16ToI128::new())),
3170 (PortType::F32, PortType::U8) => Some(Box::new(C::F32ToU8::new())),
3171 (PortType::F32, PortType::I8) => Some(Box::new(C::F32ToI8::new())),
3172 (PortType::F32, PortType::U16) => Some(Box::new(C::F32ToU16::new())),
3173 (PortType::F32, PortType::I16) => Some(Box::new(C::F32ToI16::new())),
3174 (PortType::F32, PortType::U128) => Some(Box::new(C::F32ToU128::new())),
3175 (PortType::F32, PortType::I128) => Some(Box::new(C::F32ToI128::new())),
3176 (PortType::I64, PortType::F16) => Some(Box::new(C::I64ToF16::new())),
3177 (PortType::U128, PortType::U8) => Some(Box::new(C::U128ToU8::new())),
3178 (PortType::U128, PortType::I8) => Some(Box::new(C::U128ToI8::new())),
3179 (PortType::U128, PortType::U16) => Some(Box::new(C::U128ToU16::new())),
3180 (PortType::U128, PortType::I16) => Some(Box::new(C::U128ToI16::new())),
3181 (PortType::U128, PortType::F16) => Some(Box::new(C::U128ToF16::new())),
3182 (PortType::U128, PortType::U32) => Some(Box::new(C::U128ToU32::new())),
3183 (PortType::U128, PortType::I32) => Some(Box::new(C::U128ToI32::new())),
3184 (PortType::U128, PortType::F32) => Some(Box::new(C::U128ToF32::new())),
3185 (PortType::U128, PortType::I64) => Some(Box::new(C::U128ToI64::new())),
3186 (PortType::I128, PortType::U8) => Some(Box::new(C::I128ToU8::new())),
3187 (PortType::I128, PortType::I8) => Some(Box::new(C::I128ToI8::new())),
3188 (PortType::I128, PortType::U16) => Some(Box::new(C::I128ToU16::new())),
3189 (PortType::I128, PortType::I16) => Some(Box::new(C::I128ToI16::new())),
3190 (PortType::I128, PortType::F16) => Some(Box::new(C::I128ToF16::new())),
3191 (PortType::I128, PortType::U32) => Some(Box::new(C::I128ToU32::new())),
3192 (PortType::I128, PortType::I32) => Some(Box::new(C::I128ToI32::new())),
3193 (PortType::I128, PortType::F32) => Some(Box::new(C::I128ToF32::new())),
3194 (PortType::I128, PortType::U64) => Some(Box::new(C::I128ToU64::new())),
3195
3196 (PortType::VecI16, PortType::VecI8) => Some(Box::new(C::VecI16ToVecI8::new())),
3200 (PortType::VecI16, PortType::VecF16) => Some(Box::new(C::VecI16ToVecF16::new())),
3201 (PortType::VecI32, PortType::VecI8) => Some(Box::new(C::VecI32ToVecI8::new())),
3202 (PortType::VecI32, PortType::VecI16) => Some(Box::new(C::VecI32ToVecI16::new())),
3203 (PortType::VecI32, PortType::VecF16) => Some(Box::new(C::VecI32ToVecF16::new())),
3204 (PortType::VecI64, PortType::VecI8) => Some(Box::new(C::VecI64ToVecI8::new())),
3205 (PortType::VecI64, PortType::VecI16) => Some(Box::new(C::VecI64ToVecI16::new())),
3206 (PortType::VecI64, PortType::VecI32) => Some(Box::new(C::VecI64ToVecI32::new())),
3207 (PortType::VecI64, PortType::VecF16) => Some(Box::new(C::VecI64ToVecF16::new())),
3208 (PortType::VecI64, PortType::VecF32) => Some(Box::new(C::VecI64ToVecF32::new())),
3209 (PortType::VecF16, PortType::VecI8) => Some(Box::new(C::VecF16ToVecI8::new())),
3210 (PortType::VecF16, PortType::VecI16) => Some(Box::new(C::VecF16ToVecI16::new())),
3211 (PortType::VecF16, PortType::VecI32) => Some(Box::new(C::VecF16ToVecI32::new())),
3212 (PortType::VecF16, PortType::VecI64) => Some(Box::new(C::VecF16ToVecI64::new())),
3213 (PortType::VecF32, PortType::VecI8) => Some(Box::new(C::VecF32ToVecI8::new())),
3214 (PortType::VecF32, PortType::VecI16) => Some(Box::new(C::VecF32ToVecI16::new())),
3215 (PortType::VecF32, PortType::VecI64) => Some(Box::new(C::VecF32ToVecI64::new())),
3216 (PortType::VecF32, PortType::VecF16) => Some(Box::new(C::VecF32ToVecF16::new())),
3217 (PortType::VecF64, PortType::VecI8) => Some(Box::new(C::VecF64ToVecI8::new())),
3218 (PortType::VecF64, PortType::VecI16) => Some(Box::new(C::VecF64ToVecI16::new())),
3219 (PortType::VecF64, PortType::VecI32) => Some(Box::new(C::VecF64ToVecI32::new())),
3220 (PortType::VecF64, PortType::VecI64) => Some(Box::new(C::VecF64ToVecI64::new())),
3221 (PortType::VecF64, PortType::VecF16) => Some(Box::new(C::VecF64ToVecF16::new())),
3222 (PortType::VecF64, PortType::VecF32) => Some(Box::new(C::VecF64ToVecF32::new())),
3223 (PortType::Bytes, PortType::VecF64) => Some(Box::new(C::BytesToVecF64::new())),
3224 (PortType::Bytes, PortType::VecI64) => Some(Box::new(C::BytesToVecI64::new())),
3225 (PortType::Bytes, PortType::VecF16) => Some(Box::new(C::BytesToVecF16::new())),
3226 (PortType::Bytes, PortType::VecI16) => Some(Box::new(C::BytesToVecI16::new())),
3227 (PortType::Bytes, PortType::VecI8) => Some(Box::new(C::BytesToVecI8::new())),
3228 (PortType::VecF64, PortType::Json) => Some(Box::new(C::VecF64ToJson::new())),
3229 (PortType::VecF16, PortType::Json) => Some(Box::new(C::VecF16ToJson::new())),
3230 (PortType::Json, PortType::VecF64) => Some(Box::new(C::JsonToVecF64::new())),
3231 (PortType::Json, PortType::VecI64) => Some(Box::new(C::JsonToVecI64::new())),
3232 (PortType::Json, PortType::VecF16) => Some(Box::new(C::JsonToVecF16::new())),
3233 (PortType::Json, PortType::VecI16) => Some(Box::new(C::JsonToVecI16::new())),
3234 (PortType::Json, PortType::VecI8) => Some(Box::new(C::JsonToVecI8::new())),
3235 (PortType::VecF64, PortType::Str) => Some(Box::new(C::VecF64ToStr::new())),
3236 (PortType::VecF16, PortType::Str) => Some(Box::new(C::VecF16ToStr::new())),
3237 (PortType::Str, PortType::VecF64) => Some(Box::new(C::StrToVecF64::new())),
3238 (PortType::Str, PortType::VecI64) => Some(Box::new(C::StrToVecI64::new())),
3239 (PortType::Str, PortType::VecF16) => Some(Box::new(C::StrToVecF16::new())),
3240 (PortType::Str, PortType::VecI16) => Some(Box::new(C::StrToVecI16::new())),
3241 (PortType::Str, PortType::VecI8) => Some(Box::new(C::StrToVecI8::new())),
3242
3243 _ => None,
3244 }
3245}
3246
3247use crate::compile::select::{Engine, KernelError, Provenance};
3250use crate::kernel::Kernel;
3251
3252fn fixed_outputs(resolved: &ResolvedDag) -> std::collections::HashSet<String> {
3255 let classes = PolydatProgram::classify_lifecycle(
3256 &resolved.nodes,
3257 &resolved.wiring,
3258 &resolved.input_defs,
3259 &resolved.output_map,
3260 &resolved.output_modifiers,
3261 );
3262 resolved
3263 .output_map
3264 .iter()
3265 .filter(|(name, (node, _))| {
3266 resolved.const_outputs.contains(*name)
3267 || classes.lifecycle[*node] == crate::kernel::EvalLifecycle::CompileConst
3268 })
3269 .map(|(name, _)| name.clone())
3270 .collect()
3271}
3272
3273impl PolydatAssembler {
3274 pub fn compile_with(self, engine: Engine) -> Result<Box<dyn Kernel>, KernelError> {
3282 self.compile_engine_with_log(engine, None)
3283 }
3284
3285 pub fn compile_kernel(self) -> Result<Box<dyn Kernel>, KernelError> {
3288 self.compile_with(Engine::default())
3289 }
3290
3291 pub fn compile_engine_with_log(
3294 self,
3295 engine: Engine,
3296 log: Option<&mut crate::dsl::events::CompileEventLog>,
3297 ) -> Result<Box<dyn Kernel>, KernelError> {
3298 match engine {
3299 Engine::Interpreter(cones) => {
3302 let mut asm = self;
3303 asm.jit_mode = Some(cones);
3304 Ok(Box::new(asm.compile_with_log(log)?))
3305 }
3306 _ => Ok(self.compile_slots_with_log(engine, log)?),
3310 }
3311 }
3312
3313 pub fn compile_slots(
3329 self,
3330 engine: Engine,
3331 ) -> Result<Box<dyn crate::compile::SlotKernel>, KernelError> {
3332 self.compile_slots_with_log(engine, None)
3333 }
3334
3335 pub fn compile_slots_with_log(
3338 self,
3339 engine: Engine,
3340 log: Option<&mut crate::dsl::events::CompileEventLog>,
3341 ) -> Result<Box<dyn crate::compile::SlotKernel>, KernelError> {
3342 let template = self.template;
3343 let mut kernel = self.build_slots_with_log(engine, log)?;
3344 if !template {
3345 crate::kernel::Kernel::init(kernel.as_mut())?;
3346 }
3347 Ok(kernel)
3348 }
3349
3350 fn build_slots_with_log(
3352 self,
3353 engine: Engine,
3354 mut log: Option<&mut crate::dsl::events::CompileEventLog>,
3355 ) -> Result<Box<dyn crate::compile::SlotKernel>, KernelError> {
3356 let refused = |reason: String| KernelError::Refused { engine, reason };
3357 let asked = |e: KernelError| match e {
3362 KernelError::Refused { reason, .. } => KernelError::Refused { engine, reason },
3363 other => other,
3364 };
3365 let strict = self.strict;
3366 match engine {
3367 Engine::Interpreter(_) => Err(refused(
3368 "the interpreter has no slot buffer: its buffers are typed `Value`s, so \
3369 there is no slot to name. Ask for `closures`, `native` or `pure-native` \
3370 for the slot surface, or compile with `compile_with` and drive the \
3371 kernel through the `Kernel` trait, which every engine answers."
3372 .into(),
3373 )),
3374 Engine::Closures(prov) => {
3375 let resolved = self.resolve_with_log(log.as_deref_mut())?;
3376 if strict {
3377 Self::refuse_strict(&resolved)?;
3378 }
3379 let folded = log.is_some().then(|| Self::constant_sites(&resolved));
3380 let (node_total, output_total) =
3381 (resolved.nodes.len(), resolved.output_order.len());
3382 let kernel = Self::closures_from(resolved, prov).map_err(asked)?;
3383 Self::log_folded(kernel.as_ref(), folded, log.as_deref_mut());
3384 Self::log_summary(log, node_total, output_total);
3385 Ok(kernel)
3386 }
3387 Engine::Native(prov) => {
3396 {
3397 let resolved = self.resolve_with_log(log.as_deref_mut())?;
3398 if strict {
3399 Self::refuse_strict(&resolved)?;
3400 }
3401 let folded = log.is_some().then(|| Self::constant_sites(&resolved));
3402 let (node_total, output_total) =
3403 (resolved.nodes.len(), resolved.output_order.len());
3404 if prov == Provenance::Push {
3410 return Err(refused(
3411 "native code has no push-only kernel: push-side invalidation \
3412 without the cone guard has no native form. Ask for `pushpull` \
3413 for both, `pull` for the guard alone, or `auto` to let the \
3414 selector choose; `push` alone is available on the closure tier."
3415 .into(),
3416 ));
3417 }
3418 let prov = Self::provenance_for(prov, &resolved);
3419 let kernel = Self::hybrid_from(resolved).map_err(asked)?;
3420 let kernel: Box<dyn crate::compile::SlotKernel> = match prov {
3421 Provenance::Raw => Box::new(kernel.into_raw()),
3422 Provenance::Pull => Box::new(kernel.into_pull()),
3423 Provenance::PushPull | Provenance::Auto => Box::new(kernel),
3429 Provenance::Push => {
3430 return Err(refused("native code has no push-only kernel".into()));
3431 }
3432 };
3433 Self::log_folded(kernel.as_ref(), folded, log.as_deref_mut());
3434 Self::log_summary(log, node_total, output_total);
3435 Ok(kernel)
3436 }
3437 }
3438 Engine::PureNative(prov) => {
3439 #[cfg(feature = "jit")]
3440 {
3441 let resolved = self.resolve_with_log(log.as_deref_mut())?;
3442 if strict {
3443 Self::refuse_strict(&resolved)?;
3444 }
3445 let folded = log.is_some().then(|| Self::constant_sites(&resolved));
3446 let (node_total, output_total) =
3447 (resolved.nodes.len(), resolved.output_order.len());
3448 let prov = match prov {
3455 Provenance::Auto => match Self::provenance_for(prov, &resolved) {
3456 Provenance::Raw => Provenance::Raw,
3457 _ => Provenance::PushPull,
3458 },
3459 named @ (Provenance::Raw | Provenance::PushPull) => named,
3460 other => {
3461 return Err(refused(format!(
3462 "pure native code has no {} kernel: the tier keeps only the \
3463 two forms the differential needs. Ask for `raw` or \
3464 `pushpull`, or `auto` to let the selector choose; every \
3465 mode is available on `native`.",
3466 format!("{other:?}").to_lowercase(),
3467 )));
3468 }
3469 };
3470 let kernel: Box<dyn crate::compile::SlotKernel> = match prov {
3471 Provenance::Raw => Box::new(Self::jit_raw_from(resolved).map_err(asked)?),
3472 _ => Box::new(Self::jit_push_pull_from(resolved).map_err(asked)?),
3473 };
3474 Self::log_folded(kernel.as_ref(), folded, log.as_deref_mut());
3475 Self::log_summary(log, node_total, output_total);
3476 Ok(kernel)
3477 }
3478 #[cfg(not(feature = "jit"))]
3479 {
3480 let _ = (prov, log);
3481 Err(refused(
3482 "this build has no native code (the `jit` feature is off)".into(),
3483 ))
3484 }
3485 }
3486 }
3487 }
3488
3489 fn constant_sites(resolved: &ResolvedDag) -> Vec<(String, usize, crate::ast::PortType)> {
3494 let classes = PolydatProgram::classify_lifecycle(
3495 &resolved.nodes,
3496 &resolved.wiring,
3497 &resolved.input_defs,
3498 &resolved.output_map,
3499 &resolved.output_modifiers,
3500 );
3501 let layout = slot_layout(resolved);
3502 resolved
3503 .nodes
3504 .iter()
3505 .enumerate()
3506 .filter(|(i, n)| {
3507 classes.lifecycle[*i] == crate::kernel::EvalLifecycle::CompileConst
3508 && n.meta().outs.len() == 1
3509 })
3510 .map(|(i, n)| {
3511 (
3512 n.meta().name.clone(),
3513 layout.port_offsets[i][0],
3514 n.meta().outs[0].typ,
3515 )
3516 })
3517 .collect()
3518 }
3519
3520 fn log_folded(
3523 kernel: &dyn Kernel,
3524 sites: Option<Vec<(String, usize, crate::ast::PortType)>>,
3525 log: Option<&mut crate::dsl::events::CompileEventLog>,
3526 ) {
3527 let (Some(sites), Some(log)) = (sites, log) else {
3528 return;
3529 };
3530 for (node, slot, ty) in sites {
3531 let value = crate::kernel::KernelInternals::slot_value(kernel, slot, ty);
3532 if !matches!(value, crate::ast::Value::None) {
3533 log.push(crate::dsl::events::CompileEvent::ConstantFolded {
3534 node,
3535 value: value.to_display_string(),
3536 });
3537 }
3538 }
3539 }
3540
3541 fn provenance_for(prov: Provenance, resolved: &ResolvedDag) -> Provenance {
3546 match prov {
3547 Provenance::Auto => {
3548 let analysis =
3549 select::analyze_graph(&resolved.nodes, &resolved.wiring, &resolved.output_map);
3550 match select::select_prov_mode(&analysis) {
3551 ProvMode::Raw => Provenance::Raw,
3552 ProvMode::Pull => Provenance::Pull,
3553 ProvMode::PushPull => Provenance::PushPull,
3554 }
3555 }
3556 p => p,
3557 }
3558 }
3559
3560 fn closures_from(
3563 resolved: ResolvedDag,
3564 prov: Provenance,
3565 ) -> Result<Box<dyn crate::compile::SlotKernel>, KernelError> {
3566 let prov = Self::provenance_for(prov, &resolved);
3567 let (coord_count, total_slots, steps, output_map, ref_slots, extras) =
3568 Self::build_p2_layout(&resolved).map_err(Self::refused_by_closures)?;
3569 let dependents = || {
3570 slot_layout(&resolved).expand_dependents(
3571 &resolved,
3572 &PolydatProgram::compute_dependents(
3573 &PolydatProgram::compute_provenance(&resolved.nodes, &resolved.wiring),
3574 resolved.input_defs.len(),
3575 ),
3576 )
3577 };
3578 Ok(match prov {
3579 Provenance::Raw => Box::new(CompiledKernelRaw::new(
3580 coord_count,
3581 total_slots,
3582 steps,
3583 output_map,
3584 ref_slots,
3585 extras,
3586 )?),
3587 Provenance::Push => Box::new(CompiledKernelPush::new(
3588 coord_count,
3589 total_slots,
3590 steps,
3591 output_map,
3592 dependents(),
3593 ref_slots,
3594 extras,
3595 )?),
3596 Provenance::Pull => Box::new(CompiledKernelPull::new(
3597 coord_count,
3598 total_slots,
3599 steps,
3600 output_map,
3601 &dependents(),
3602 ref_slots,
3603 extras,
3604 )?),
3605 Provenance::PushPull | Provenance::Auto => Box::new(CompiledKernelPushPull::new(
3606 coord_count,
3607 total_slots,
3608 steps,
3609 output_map,
3610 dependents(),
3611 ref_slots,
3612 extras,
3613 )?),
3614 })
3615 }
3616}
3617
3618#[cfg(test)]
3619mod strict_values_tests {
3620 use super::*;
3621 use crate::ast::{NodeMeta, Port, Slot, Value};
3622 use crate::dsl::const_constraints::ConstConstraint;
3623 use crate::dsl::events::{CompileEvent, CompileEventLog};
3624
3625 struct NonZeroSink {
3627 meta: NodeMeta,
3628 }
3629
3630 impl NonZeroSink {
3631 fn new() -> Self {
3632 let mut port = Port::u64("divisor");
3633 port.constraint = Some(ConstConstraint::NonZeroU64);
3634 NonZeroSink {
3635 meta: NodeMeta {
3636 name: "nonzero_sink".into(),
3637 ins: vec![Slot::Wire(port)],
3638 outs: vec![Port::u64("output")],
3639 },
3640 }
3641 }
3642 }
3643
3644 impl PolydatNode for NonZeroSink {
3645 fn meta(&self) -> &NodeMeta {
3646 &self.meta
3647 }
3648 fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
3649 outputs[0] = inputs[0].clone();
3650 }
3651 }
3652
3653 fn inserted(asm: PolydatAssembler) -> usize {
3654 let mut log = CompileEventLog::new();
3655 asm.resolve_with_log(Some(&mut log)).expect("resolve");
3656 log.events()
3657 .iter()
3658 .filter(|e| matches!(e, CompileEvent::AssertionInserted { .. }))
3659 .count()
3660 }
3661
3662 #[test]
3666 fn an_upstream_assertion_for_another_constraint_is_not_proof() {
3667 let mut asm = PolydatAssembler::new(vec!["cycle".into()]);
3668 asm.add_node(
3669 "ranged",
3670 crate::library::assertions::assert_value_node(
3671 PortType::U64,
3672 ConstConstraint::RangeU64 { min: 0, max: 10 },
3673 ),
3674 vec![WireRef::input("cycle")],
3675 );
3676 asm.add_node(
3677 "sink",
3678 Box::new(NonZeroSink::new()),
3679 vec![WireRef::node("ranged")],
3680 );
3681 asm.add_output("out", WireRef::node("sink"));
3682 asm.set_strict_wires(false, true);
3683 assert_eq!(inserted(asm), 1);
3684 }
3685
3686 #[test]
3689 fn a_guard_like_name_is_not_proof() {
3690 let mut asm = PolydatAssembler::new(vec!["cycle".into()]);
3691 asm.add_node(
3692 "__assert_v_0",
3693 Box::new(crate::library::identity::Identity::new(PortType::U64)),
3694 vec![WireRef::input("cycle")],
3695 );
3696 asm.add_node(
3697 "sink",
3698 Box::new(NonZeroSink::new()),
3699 vec![WireRef::node("__assert_v_0")],
3700 );
3701 asm.add_output("out", WireRef::node("sink"));
3702 asm.set_strict_wires(false, true);
3703 assert_eq!(inserted(asm), 1);
3704 }
3705
3706 #[test]
3710 fn scope_strictness_marks_only_the_nodes_added_under_it() {
3711 let mut asm = PolydatAssembler::new(vec!["cycle".into()]);
3712 let before = asm.set_scope_strict_values(Some(true));
3713 assert_eq!(before, None);
3714 asm.add_node(
3715 "inner",
3716 Box::new(NonZeroSink::new()),
3717 vec![WireRef::input("cycle")],
3718 );
3719 asm.set_scope_strict_values(before);
3720 asm.add_node(
3721 "outer",
3722 Box::new(NonZeroSink::new()),
3723 vec![WireRef::input("cycle")],
3724 );
3725 asm.add_output("a", WireRef::node("inner"));
3726 asm.add_output("b", WireRef::node("outer"));
3727 assert_eq!(inserted(asm), 1);
3728 }
3729
3730 #[test]
3734 fn a_non_strict_scope_is_unchecked_under_a_strict_program() {
3735 let mut asm = PolydatAssembler::new(vec!["cycle".into()]);
3736 let before = asm.set_scope_strict_values(Some(false));
3737 asm.add_node(
3738 "inner",
3739 Box::new(NonZeroSink::new()),
3740 vec![WireRef::input("cycle")],
3741 );
3742 asm.set_scope_strict_values(before);
3743 asm.add_node(
3744 "outer",
3745 Box::new(NonZeroSink::new()),
3746 vec![WireRef::input("cycle")],
3747 );
3748 asm.add_output("a", WireRef::node("inner"));
3749 asm.add_output("b", WireRef::node("outer"));
3750 asm.set_strict_wires(false, true);
3751 assert_eq!(inserted(asm), 1);
3752 }
3753}