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}
59
60#[derive(Debug)]
62pub enum AssemblyError {
63 UnknownWire(String),
65 TypeMismatch {
67 from_node: String,
69 from_port: usize,
71 from_type: PortType,
73 to_node: String,
75 to_port: usize,
77 to_type: PortType,
79 },
80 DuplicateNode(String),
82 CycleDetected,
84 ArityMismatch {
86 node_name: String,
88 expected: usize,
90 got: usize,
92 },
93 ConstantFold(String),
99 Other(String),
101}
102
103impl std::fmt::Display for AssemblyError {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 match self {
106 AssemblyError::UnknownWire(name) => {
107 write!(f, "unknown wire: '{name}'\n\n")?;
108 writeln!(f, " No node output or coordinate named '{name}' exists.")?;
109 write!(
110 f,
111 " Check spelling, or add a node that produces this output."
112 )
113 }
114 AssemblyError::TypeMismatch {
115 from_node,
116 from_port,
117 from_type,
118 to_node,
119 to_port,
120 to_type,
121 } => {
122 writeln!(
123 f,
124 "type mismatch: cannot connect {from_type} output to {to_type} input"
125 )?;
126 writeln!(f)?;
127 writeln!(
128 f,
129 " {from_node} [{from_port}] ──({from_type})──▶ {to_node} [{to_port}] expects {to_type}"
130 )?;
131 writeln!(f)?;
132 let suggestion = match (from_type, to_type) {
134 (PortType::U64, PortType::Str) => {
135 Some("This should auto-convert. If you see this, file a bug.")
136 }
137 (PortType::F64, PortType::Str) => {
138 Some("This should auto-convert. If you see this, file a bug.")
139 }
140 (PortType::U64, PortType::F64) => {
141 Some("This should auto-convert. If you see this, file a bug.")
142 }
143 (PortType::U64, PortType::Bytes) => {
144 Some("Add u64_to_bytes() between them to convert.")
145 }
146 (PortType::Str, PortType::Bytes) => {
147 Some("String cannot be directly used as bytes.")
148 }
149 (PortType::U64, PortType::Json) => {
150 Some("Add to_json() between them to wrap as JSON.")
151 }
152 (PortType::Str, PortType::Json) => {
153 Some("Add str_to_json() to parse the string as JSON.")
154 }
155 (PortType::Bytes, PortType::Str) => {
156 Some("Add to_hex() or to_base64() to convert bytes to string.")
157 }
158 (PortType::Bytes, PortType::U64) => {
159 Some("Bytes cannot be directly converted to u64.")
160 }
161 _ => None,
162 };
163 if let Some(hint) = suggestion {
164 write!(f, " Hint: {hint}")?;
165 }
166 Ok(())
167 }
168 AssemblyError::DuplicateNode(name) => {
169 write!(f, "duplicate node name: '{name}'\n\n")?;
170 write!(f, " Two nodes cannot share the same name.")
171 }
172 AssemblyError::CycleDetected => {
173 write!(f, "cycle detected in DAG\n\n")?;
174 writeln!(
175 f,
176 " The graph contains a loop. Polydat graphs must be acyclic"
177 )?;
178 write!(f, " (data flows in one direction only).")
179 }
180 AssemblyError::ArityMismatch {
181 node_name,
182 expected,
183 got,
184 } => {
185 write!(f, "wrong number of inputs for '{node_name}'\n\n")?;
186 writeln!(f, " Expected {expected} input(s), but got {got}.")?;
187 if *got < *expected {
188 write!(f, " Connect more wires to this node's input ports.")
189 } else {
190 write!(f, " Disconnect extra wires from this node.")
191 }
192 }
193 AssemblyError::ConstantFold(msg) => write!(
194 f,
195 "a value this program computes at build could not be computed: {msg}"
196 ),
197 AssemblyError::Other(msg) => write!(f, "{msg}"),
198 }
199 }
200}
201
202impl std::error::Error for AssemblyError {}
203
204pub(crate) struct ResolvedDag {
206 pub(crate) nodes: Vec<Box<dyn PolydatNode>>,
208 pub(crate) wiring: Vec<Vec<WireSource>>,
210 pub(crate) input_defs: Vec<crate::kernel::InputDef>,
212 pub(crate) coord_count: usize,
214 pub(crate) output_map: HashMap<String, (usize, usize)>,
216 pub(crate) output_order: Vec<String>,
218 pub(crate) source: String,
220 pub(crate) context: String,
222 pub(crate) output_modifiers: HashMap<String, crate::dsl::ast::BindingModifier>,
224 pub(crate) const_outputs: std::collections::HashSet<String>,
226 pub(crate) cursor_schemas: Vec<crate::iteration::source::SourceSchema>,
228 pub(crate) ledger: std::sync::Arc<crate::kernel::CompileLedger>,
230}
231
232impl ResolvedDag {
233 fn input_names(&self) -> Vec<String> {
235 self.input_defs[..self.coord_count]
236 .iter()
237 .map(|d| d.name.clone())
238 .collect()
239 }
240}
241
242struct SlotLayout {
247 input_starts: Vec<usize>,
249 coord_slots: usize,
251 port_offsets: Vec<Vec<usize>>,
253 total_slots: usize,
255}
256
257fn slot_layout(resolved: &ResolvedDag) -> SlotLayout {
258 let mut input_starts = Vec::with_capacity(resolved.coord_count);
259 let mut next = 0usize;
260 for d in &resolved.input_defs {
261 input_starts.push(next);
262 next += d.port_type.slot_width();
263 }
264 let coord_slots = next;
265 let mut port_offsets: Vec<Vec<usize>> = Vec::with_capacity(resolved.nodes.len());
266 for node in &resolved.nodes {
267 let mut po = Vec::with_capacity(node.meta().outs.len());
268 for out in &node.meta().outs {
269 po.push(next);
270 next += out.typ.slot_width();
271 }
272 port_offsets.push(po);
273 }
274 SlotLayout {
275 input_starts,
276 coord_slots,
277 port_offsets,
278 total_slots: next,
279 }
280}
281
282fn node_step_op(
288 node: &dyn crate::ast::PolydatNode,
289 wire_types: &[PortType],
290) -> Option<(
291 crate::compile::closures::StepOp,
292 Vec<crate::ast::ScratchElem>,
293)> {
294 let meta = node.meta();
298 if (meta.name == "identity" || meta.name.starts_with("__port_")) && meta.outs.len() == 1 {
299 return Some(match meta.outs[0].typ.slot_color() {
300 crate::ast::SlotColor::Ref2 => {
301 let kit = ref_copy_kit(meta.outs[0].typ)?;
302 (crate::compile::closures::StepOp::Slot(kit.op), kit.scratch)
303 }
304 _ => (crate::compile::closures::StepOp::Copy, Vec::new()),
305 });
306 }
307 if let Some(op) = node.compiled_u64() {
308 return Some((crate::compile::closures::StepOp::U64(op), Vec::new()));
309 }
310 node.compiled_slot(
311 wire_types,
312 crate::compile::select::Engine::Closures(crate::compile::select::Provenance::Auto),
313 )
314 .map(|kit| (crate::compile::closures::StepOp::Slot(kit.op), kit.scratch))
315}
316
317pub(crate) fn scratch_pairs(
328 name: &str,
329 ref_starts: &[usize],
330 scratch: &[crate::ast::ScratchElem],
331 base: usize,
332) -> Vec<(usize, usize)> {
333 use crate::ast::ScratchElem;
334 let publishing: Vec<usize> = scratch
335 .iter()
336 .enumerate()
337 .filter(|(_, e)| {
338 !matches!(
339 e,
340 ScratchElem::Slots | ScratchElem::Kernels | ScratchElem::State
341 )
342 })
343 .map(|(k, _)| base + k)
344 .collect();
345 assert!(
346 publishing.len() <= ref_starts.len(),
347 "slot-op step '{name}' declares {} publishing scratch entries for {} Ref output ports",
348 publishing.len(),
349 ref_starts.len()
350 );
351 ref_starts.iter().copied().zip(publishing).collect()
352}
353
354pub(crate) fn ref_copy_kit(ty: PortType) -> Option<crate::ast::CompiledSlotKit> {
360 use crate::ast::ScratchBuf;
361 let elem = ty.scratch_elem()?;
362 Some(crate::ast::CompiledSlotKit {
363 scratch: vec![elem],
364 op: Box::new(
365 move |inputs: &[u64], outputs: &mut [u64], scratch: &mut [ScratchBuf]| {
366 let (p, n) = (inputs[0] as usize, inputs[1] as usize);
367 macro_rules! copy_into {
368 ($v:expr, $t:ty) => {{
369 $v.clear();
370 $v.extend_from_slice(unsafe {
374 std::slice::from_raw_parts(p as *const $t, n)
375 });
376 }};
377 }
378 match &mut scratch[0] {
379 ScratchBuf::Str(v) | ScratchBuf::Bytes(v) => copy_into!(v, u8),
380 ScratchBuf::F32(v) => copy_into!(v, f32),
381 ScratchBuf::F64(v) => copy_into!(v, f64),
382 ScratchBuf::F16(v) => copy_into!(v, half::f16),
383 ScratchBuf::I8(v) => copy_into!(v, i8),
384 ScratchBuf::I16(v) => copy_into!(v, i16),
385 ScratchBuf::I32(v) => copy_into!(v, i32),
386 ScratchBuf::I64(v) => copy_into!(v, i64),
387 ScratchBuf::Value(v) => {
388 v.clear();
389 if n > 0 {
390 v.push(unsafe { (*(p as *const crate::ast::Value)).clone() });
392 }
393 }
394 ScratchBuf::Slots(_) | ScratchBuf::Kernels(_) | ScratchBuf::State(_) => {
395 unreachable!("a copy owns only a value entry")
396 }
397 }
398 let (ptr, len) = scratch[0].ptr_len();
399 outputs[0] = ptr;
400 outputs[1] = len;
401 },
402 ),
403 })
404}
405
406pub(crate) fn identity_op(node: &dyn crate::ast::PolydatNode) -> Option<crate::ast::CompiledU64Op> {
412 let meta = node.meta();
413 if meta.name != "identity" || meta.outs.len() != 1 {
414 return None;
415 }
416 if meta.outs[0].typ.slot_color() == crate::ast::SlotColor::Ref2 {
417 return None;
418 }
419 Some(Box::new(|inputs: &[u64], outputs: &mut [u64]| {
420 outputs.copy_from_slice(inputs)
421 }))
422}
423
424impl SlotLayout {
425 fn input_slots(&self, resolved: &ResolvedDag, node_idx: usize) -> Vec<usize> {
428 let mut slots = Vec::new();
429 for source in &resolved.wiring[node_idx] {
430 let (start, w) = match source {
431 WireSource::Input(c) => (
432 self.input_starts.get(*c).copied().unwrap_or(*c),
433 resolved
434 .input_defs
435 .get(*c)
436 .map(|d| d.port_type.slot_width())
437 .unwrap_or(1),
438 ),
439 WireSource::NodeOutput(u, p) => (
440 self.port_offsets[*u][*p],
441 resolved.nodes[*u].meta().outs[*p].typ.slot_width(),
442 ),
443 };
444 slots.extend(start..start + w);
445 }
446 slots
447 }
448
449 fn output_slots(&self, resolved: &ResolvedDag, node_idx: usize) -> Vec<usize> {
451 let mut slots = Vec::new();
452 for (p, out) in resolved.nodes[node_idx].meta().outs.iter().enumerate() {
453 let start = self.port_offsets[node_idx][p];
454 slots.extend(start..start + out.typ.slot_width());
455 }
456 slots
457 }
458
459 fn named_outputs(&self, resolved: &ResolvedDag) -> HashMap<String, usize> {
461 resolved
462 .output_map
463 .iter()
464 .map(|(name, (n, p))| (name.clone(), self.port_offsets[*n][*p]))
465 .collect()
466 }
467
468 fn ref_slot_mask(&self, resolved: &ResolvedDag) -> Vec<bool> {
474 use crate::ast::SlotColor;
475 let mut mask = vec![false; self.total_slots];
476 let mut mark = |start: usize, color: SlotColor| match color {
477 SlotColor::Ref2 => {
478 mask[start] = true;
479 mask[start + 1] = true;
480 }
481 SlotColor::Imm1 | SlotColor::Imm2 => {}
482 };
483 for (i, d) in resolved.input_defs.iter().enumerate() {
484 mark(self.input_starts[i], d.port_type.slot_color());
485 }
486 for (n, node) in resolved.nodes.iter().enumerate() {
487 for (p, out) in node.meta().outs.iter().enumerate() {
488 mark(self.port_offsets[n][p], out.typ.slot_color());
489 }
490 }
491 mask
492 }
493
494 fn ref_output_starts(&self, resolved: &ResolvedDag, node_idx: usize) -> Vec<usize> {
498 resolved.nodes[node_idx]
499 .meta()
500 .outs
501 .iter()
502 .enumerate()
503 .filter(|(_, out)| out.typ.slot_color() == crate::ast::SlotColor::Ref2)
504 .map(|(p, _)| self.port_offsets[node_idx][p])
505 .collect()
506 }
507
508 fn expand_dependents(&self, resolved: &ResolvedDag, deps: &[Vec<usize>]) -> Vec<Vec<usize>> {
514 let mut out = Vec::with_capacity(self.coord_slots);
515 for (i, d) in resolved.input_defs.iter().enumerate() {
516 for _ in 0..d.port_type.slot_width() {
517 out.push(deps.get(i).cloned().unwrap_or_default());
518 }
519 }
520 out
521 }
522}
523
524pub struct PolydatAssembler {
526 input_defs: Vec<crate::kernel::InputDef>,
528 coord_count: usize,
530 nodes: Vec<PendingNode>,
531 output_order: Vec<String>,
533 outputs: HashMap<String, WireRef>,
534 source: String,
536 context: String,
538 output_modifiers: HashMap<String, crate::dsl::ast::BindingModifier>,
540 const_outputs: std::collections::HashSet<String>,
543 pub(crate) strict_values: bool,
548 pub(crate) strict_types: bool,
556 pub(crate) strict: bool,
561 pub(crate) jit_mode: Option<crate::compile::cone::JitMode>,
566 pub(crate) ledger: std::sync::Arc<crate::kernel::CompileLedger>,
570 cursor_schemas: Vec<crate::iteration::source::SourceSchema>,
574}
575
576type P2Layout = (
580 usize,
581 usize,
582 Vec<crate::compile::closures::P2Step>,
583 HashMap<String, usize>,
584 Vec<bool>,
585 crate::compile::closures::P2Extras,
586);
587
588#[cfg(feature = "jit")]
594type JitLayout = (
595 usize,
596 usize,
597 Vec<(crate::compile::jit::JitOp, Vec<usize>, Vec<usize>)>,
598 HashMap<String, usize>,
599 crate::compile::jit::ScratchPlan,
600 Vec<usize>,
601);
602
603impl PolydatAssembler {
604 pub fn new(input_names: Vec<String>) -> Self {
606 let coord_count = input_names.len();
607 let input_defs: Vec<crate::kernel::InputDef> = input_names
608 .into_iter()
609 .map(|name| crate::kernel::InputDef {
610 name,
611 default: crate::ast::Value::U64(0),
612 port_type: crate::ast::PortType::U64,
613 kind: crate::kernel::InputKind::Coordinate,
614 })
615 .collect();
616 Self {
617 input_defs,
618 coord_count,
619 nodes: Vec::new(),
620 output_order: Vec::new(),
621 outputs: HashMap::new(),
622 source: String::new(),
623 context: "(assembler)".into(),
624 output_modifiers: HashMap::new(),
625 const_outputs: std::collections::HashSet::new(),
626 strict_values: false,
627 strict_types: false,
628 strict: false,
629 jit_mode: None,
630 cursor_schemas: Vec::new(),
631 ledger: crate::kernel::CompileLedger::new(),
632 }
633 }
634
635 pub fn set_cursor_schemas(&mut self, schemas: Vec<crate::iteration::source::SourceSchema>) {
640 self.cursor_schemas = schemas;
641 }
642
643 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
645 &self.cursor_schemas
646 }
647
648 pub fn set_strict_wires(&mut self, strict_types: bool, strict_values: bool) {
652 self.strict_types = strict_types;
653 self.strict_values = strict_values;
654 }
655
656 pub fn set_strict(&mut self, strict: bool) {
662 self.strict = strict;
663 }
664
665 pub fn set_jit_mode(&mut self, mode: crate::compile::cone::JitMode) {
668 self.jit_mode = Some(mode);
669 }
670
671 pub fn set_context(&mut self, source: &str, context: &str) {
674 self.source = source.to_string();
675 self.context = context.to_string();
676 }
677
678 pub fn add_node(
680 &mut self,
681 name: impl Into<String>,
682 node: Box<dyn PolydatNode>,
683 inputs: Vec<WireRef>,
684 ) -> &mut Self {
685 self.nodes.push(PendingNode {
686 name: name.into(),
687 node,
688 inputs,
689 });
690 self
691 }
692
693 pub fn set_output_modifier(&mut self, name: &str, modifier: crate::dsl::ast::BindingModifier) {
695 if modifier != crate::dsl::ast::BindingModifier::NONE {
696 self.output_modifiers.insert(name.to_string(), modifier);
697 }
698 }
699
700 pub fn mark_const_output(&mut self, name: &str) {
705 self.const_outputs.insert(name.to_string());
706 }
707
708 pub fn node_count(&self) -> usize {
710 self.nodes.len()
711 }
712
713 pub fn add_output(&mut self, name: impl Into<String>, wire: WireRef) -> &mut Self {
715 let name = name.into();
716 if !self.outputs.contains_key(&name) {
717 self.output_order.push(name.clone());
718 }
719 self.outputs.insert(name, wire);
720 self
721 }
722
723 pub fn add_input(
734 &mut self,
735 name: impl Into<String>,
736 default: crate::ast::Value,
737 port_type: crate::ast::PortType,
738 kind: crate::kernel::InputKind,
739 ) -> &mut Self {
740 self.input_defs.push(crate::kernel::InputDef {
741 name: name.into(),
742 default,
743 port_type,
744 kind,
745 });
746 self
747 }
748
749 pub fn set_input_type(&mut self, name: &str, port_type: crate::ast::PortType) {
754 if let Some(d) = self.input_defs.iter_mut().find(|d| d.name == name) {
755 d.port_type = port_type;
756 }
757 }
758
759 pub fn input_names(&self) -> Vec<&str> {
761 self.input_defs.iter().map(|d| d.name.as_str()).collect()
762 }
763
764 pub fn node_output_type(&self, name: &str) -> Option<crate::ast::PortType> {
769 self.nodes
770 .iter()
771 .find(|n| n.name == name)
772 .and_then(|n| n.node.meta().outs.first())
773 .map(|p| p.typ)
774 }
775
776 pub fn output_names(&self) -> Vec<&str> {
778 self.outputs.keys().map(|s| s.as_str()).collect()
779 }
780
781 pub fn node_type_of(&self, name: &str) -> Option<String> {
783 self.nodes
784 .iter()
785 .find(|pn| pn.name == name)
786 .map(|pn| pn.node.meta().name.clone())
787 }
788
789 pub fn output_type(&self, name: &str) -> Option<PortType> {
793 self.nodes
794 .iter()
795 .find(|pn| pn.name == name)
796 .and_then(|pn| pn.node.meta().outs.first())
797 .map(|port| port.typ)
798 }
799
800 pub fn input_type(&self, name: &str) -> Option<PortType> {
802 self.input_defs
803 .iter()
804 .find(|d| d.name == name)
805 .map(|d| d.port_type)
806 }
807
808 pub fn wire_type(&self, wire: &WireRef) -> Option<PortType> {
813 match wire {
814 WireRef::Input(name) => self.input_type(name),
815 WireRef::Node(name, port_idx) => self
816 .nodes
817 .iter()
818 .find(|pn| &pn.name == name)
819 .and_then(|pn| pn.node.meta().outs.get(*port_idx))
820 .map(|p| p.typ),
821 }
822 }
823
824 pub fn compile(self) -> Result<PolydatKernel, AssemblyError> {
826 self.compile_with_log(None)
827 }
828
829 pub fn compile_with_log(
831 self,
832 mut log: Option<&mut crate::dsl::events::CompileEventLog>,
833 ) -> Result<PolydatKernel, AssemblyError> {
834 let jit_mode = self.jit_mode.unwrap_or_default();
835 let strict = self.strict;
836 let mut resolved = self.resolve_with_log(log.as_deref_mut())?;
837 let (node_total, output_total) = (resolved.nodes.len(), resolved.output_order.len());
838 crate::compile::cone::extract_jit_cones(&mut resolved, jit_mode);
839 let _coord_names = resolved.input_names();
840 let modifiers = resolved.output_modifiers.clone();
841 let cursors = std::mem::take(&mut resolved.cursor_schemas);
842 let mut kernel = PolydatKernel::new_with_inputs(
843 resolved.nodes,
844 resolved.wiring,
845 resolved.input_defs,
846 resolved.coord_count,
847 resolved.output_map,
848 resolved.output_order,
849 resolved.const_outputs,
850 modifiers,
851 &resolved.source,
852 &resolved.context,
853 log.as_deref_mut(),
854 strict,
855 resolved.ledger.clone(),
856 )?;
857 if !cursors.is_empty() {
858 kernel.set_cursor_schemas(cursors);
859 }
860 kernel.set_cone_mode(jit_mode);
861 Self::log_summary(log, node_total, output_total);
862 Ok(kernel)
863 }
864
865 fn refuse_strict(resolved: &ResolvedDag) -> Result<(), AssemblyError> {
869 let classes = PolydatProgram::classify_lifecycle(
870 &resolved.nodes,
871 &resolved.wiring,
872 &resolved.input_defs,
873 &resolved.output_map,
874 &resolved.output_modifiers,
875 );
876 let is_init: Vec<bool> = classes
877 .lifecycle
878 .iter()
879 .map(|lc| *lc == crate::kernel::EvalLifecycle::CompileConst)
880 .collect();
881 match PolydatProgram::strict_violation(
882 &resolved.nodes,
883 &resolved.wiring,
884 &is_init,
885 &resolved.output_map,
886 &resolved.output_modifiers,
887 ) {
888 Some(violation) => Err(AssemblyError::Other(violation)),
889 None => Ok(()),
890 }
891 }
892
893 fn refused_by_closures(reason: String) -> KernelError {
896 KernelError::Refused {
897 engine: Engine::Closures(Provenance::Auto),
898 reason,
899 }
900 }
901
902 fn refused_by_native(reason: String) -> KernelError {
905 KernelError::Refused {
906 engine: Engine::Native(Provenance::Auto),
907 reason,
908 }
909 }
910
911 #[cfg_attr(not(feature = "jit"), allow(dead_code))]
916 fn refused_by_pure_native(reason: String) -> KernelError {
917 KernelError::Refused {
918 engine: Engine::PureNative(Provenance::Auto),
919 reason,
920 }
921 }
922
923 fn build_p2_layout(resolved: &ResolvedDag) -> Result<P2Layout, String> {
926 let layout = slot_layout(resolved);
927
928 let mut compiled_ops = Vec::with_capacity(resolved.nodes.len());
929 let mut extras = crate::compile::closures::P2Extras::default();
930 for (node_idx, node) in resolved.nodes.iter().enumerate() {
931 compiled_ops.push(
932 node_step_op(node.as_ref(), &wire_types_of(resolved, node_idx)).ok_or_else(
933 || {
934 format!(
935 "node '{}' has no compiled form (docs/design/engines.md §8)",
936 node.meta().name
937 )
938 },
939 )?,
940 );
941 }
942 extras.externs = crate::compile::externs::Externs::new(
943 &resolved.input_defs,
944 resolved.coord_count,
945 &layout.input_starts,
946 &resolved.cursor_schemas,
947 &shared_outputs_of(resolved),
948 resolved.ledger.clone(),
949 )?;
950 extras.externs.set_output_names(&resolved.output_order);
951 extras
952 .externs
953 .set_output_modifiers(&resolved.output_modifiers);
954 extras.output_types = resolved
955 .output_map
956 .iter()
957 .map(|(name, (n, p))| (name.clone(), resolved.nodes[*n].meta().outs[*p].typ))
958 .collect();
959
960 let classes = PolydatProgram::classify_lifecycle(
964 &resolved.nodes,
965 &resolved.wiring,
966 &resolved.input_defs,
967 &resolved.output_map,
968 &resolved.output_modifiers,
969 );
970 let inventory = PolydatProgram::compute_node_inventory(&resolved.nodes, &resolved.wiring);
971 let per_input = PolydatProgram::compute_dependents(
972 &inventory.input_provenance,
973 resolved.input_defs.len(),
974 );
975 extras.input_dependents = layout.expand_dependents(resolved, &per_input);
976 extras.attribution = std::sync::Arc::new(Self::attribution_of(resolved));
977
978 let mut steps = Vec::with_capacity(resolved.nodes.len());
979 for (node_idx, (op, scratch)) in compiled_ops.into_iter().enumerate() {
980 steps.push(crate::compile::closures::P2Step {
981 name: resolved.nodes[node_idx].meta().name.clone(),
982 op,
983 input_slots: layout.input_slots(resolved, node_idx),
984 output_slots: layout.output_slots(resolved, node_idx),
985 ref_output_starts: layout.ref_output_starts(resolved, node_idx),
986 scratch,
987 accepts_none: resolved.nodes[node_idx].accepts_none_inputs(),
988 volatile: classes.nondeterministic[node_idx],
989 constant: classes.lifecycle[node_idx] == crate::kernel::EvalLifecycle::CompileConst,
990 side: matches!(
991 resolved.nodes[node_idx].purity(),
992 crate::ast::Purity::SideChannel { .. }
993 ),
994 });
995 }
996 let output_map = layout.named_outputs(resolved);
997 let ref_slots = layout.ref_slot_mask(resolved);
998
999 Ok((
1000 layout.coord_slots,
1001 layout.total_slots,
1002 steps,
1003 output_map,
1004 ref_slots,
1005 extras,
1006 ))
1007 }
1008
1009 #[cfg(feature = "jit")]
1011 pub(crate) fn build_jit_layout(resolved: &ResolvedDag) -> Result<JitLayout, String> {
1012 let layout = slot_layout(resolved);
1013
1014 let mut scratch = crate::compile::jit::ScratchPlan::default();
1018 let mut jit_steps = Vec::new();
1019 for (node_idx, node) in resolved.nodes.iter().enumerate() {
1020 let mut jit_op = crate::compile::jit::classify_node_typed(
1021 node.as_ref(),
1022 &wire_types_of(resolved, node_idx),
1023 );
1024 if matches!(jit_op, crate::compile::jit::JitOp::Fallback) {
1025 return Err(format!(
1026 "node '{}' has no native form and no kit; pure native code cannot run it",
1027 node.meta().name
1028 ));
1029 }
1030 let base = scratch.elems.len();
1031 jit_op.place_scratch(base);
1032 let elems = jit_op.scratch_elems().to_vec();
1033 scratch.refs.extend(scratch_pairs(
1034 &node.meta().name,
1035 &layout.ref_output_starts(resolved, node_idx),
1036 &elems,
1037 base,
1038 ));
1039 scratch.elems.extend(elems);
1040 jit_steps.push((
1041 jit_op,
1042 layout.input_slots(resolved, node_idx),
1043 layout.output_slots(resolved, node_idx),
1044 ));
1045 }
1046
1047 let output_map = layout.named_outputs(resolved);
1048 let classes = PolydatProgram::classify_lifecycle(
1052 &resolved.nodes,
1053 &resolved.wiring,
1054 &resolved.input_defs,
1055 &resolved.output_map,
1056 &resolved.output_modifiers,
1057 );
1058 let volatile: Vec<usize> = (0..resolved.nodes.len())
1059 .filter(|&i| classes.nondeterministic[i])
1060 .collect();
1061 Ok((
1062 layout.coord_slots,
1063 layout.total_slots,
1064 jit_steps,
1065 output_map,
1066 scratch,
1067 volatile,
1068 ))
1069 }
1070
1071 #[cfg(feature = "jit")]
1074 fn jit_slot_info(resolved: &ResolvedDag) -> (Vec<bool>, HashMap<String, PortType>) {
1075 let layout = slot_layout(resolved);
1076 let guard = layout.ref_slot_mask(resolved);
1077 let types = resolved
1078 .output_map
1079 .iter()
1080 .map(|(name, (n, p))| (name.clone(), resolved.nodes[*n].meta().outs[*p].typ))
1081 .collect();
1082 (guard, types)
1083 }
1084
1085 #[cfg(feature = "jit")]
1086 fn jit_push_pull_from(
1087 resolved: ResolvedDag,
1088 ) -> Result<crate::compile::jit::JitKernelPushPull, KernelError> {
1089 let _coord_names = resolved.input_names();
1090 let (coord_count, total_slots, jit_steps, output_map, scratch, volatile) =
1091 Self::build_jit_layout(&resolved).map_err(Self::refused_by_pure_native)?;
1092 let (guard, types) = Self::jit_slot_info(&resolved);
1093 let deps = slot_layout(&resolved).expand_dependents(
1094 &resolved,
1095 &PolydatProgram::compute_dependents(
1096 &PolydatProgram::compute_provenance(&resolved.nodes, &resolved.wiring),
1097 resolved.input_defs.len(),
1098 ),
1099 );
1100 let externs = Self::externs_of(&resolved).map_err(Self::refused_by_pure_native)?;
1101 let attribution = std::sync::Arc::new(Self::attribution_of(&resolved));
1102 let (folded, origin) = Self::constant_steps(&resolved, &jit_steps);
1103 let alone = Self::side_channels(&resolved);
1104 let mut k = crate::compile::jit::compile_jit_push_pull(
1105 coord_count,
1106 total_slots,
1107 jit_steps,
1108 output_map,
1109 resolved.nodes,
1110 deps,
1111 externs,
1112 scratch,
1113 volatile,
1114 alone,
1115 )
1116 .map_err(Self::refused_by_pure_native)?;
1117 k.set_slot_info(guard, types);
1118 k.set_attribution(attribution);
1119 k.fold_constants(&folded, &origin, total_slots)?;
1122 Ok(k)
1123 }
1124
1125 #[cfg(feature = "jit")]
1133 #[allow(clippy::type_complexity)]
1134 fn constant_steps(
1135 resolved: &ResolvedDag,
1136 jit_steps: &[(crate::compile::jit::JitOp, Vec<usize>, Vec<usize>)],
1137 ) -> (
1138 Vec<(crate::compile::jit::JitOp, Vec<usize>, Vec<usize>)>,
1139 Vec<usize>,
1140 ) {
1141 let classes = PolydatProgram::classify_lifecycle(
1142 &resolved.nodes,
1143 &resolved.wiring,
1144 &resolved.input_defs,
1145 &resolved.output_map,
1146 &resolved.output_modifiers,
1147 );
1148 jit_steps
1151 .iter()
1152 .enumerate()
1153 .filter(|(i, _)| {
1154 classes.lifecycle.get(*i) == Some(&crate::kernel::EvalLifecycle::CompileConst)
1155 })
1156 .map(|(i, s)| (s.clone(), i))
1157 .unzip()
1158 }
1159
1160 #[cfg(feature = "jit")]
1164 fn side_channels(resolved: &ResolvedDag) -> Vec<bool> {
1165 resolved
1166 .nodes
1167 .iter()
1168 .map(|n| matches!(n.purity(), crate::ast::Purity::SideChannel { .. }))
1169 .collect()
1170 }
1171
1172 fn externs_of(resolved: &ResolvedDag) -> Result<crate::compile::externs::Externs, String> {
1175 let layout = slot_layout(resolved);
1176 let mut externs = crate::compile::externs::Externs::new(
1177 &resolved.input_defs,
1178 resolved.coord_count,
1179 &layout.input_starts,
1180 &resolved.cursor_schemas,
1181 &shared_outputs_of(resolved),
1182 resolved.ledger.clone(),
1183 )?;
1184 externs.set_output_names(&resolved.output_order);
1185 externs.set_output_modifiers(&resolved.output_modifiers);
1186 Ok(externs)
1187 }
1188
1189 #[doc(hidden)]
1191 #[cfg(feature = "jit")]
1192 pub(crate) fn try_compile_pure_jit_raw(
1193 self,
1194 ) -> Result<crate::compile::jit::JitKernelRaw, KernelError> {
1195 let resolved = self.resolve().map_err(KernelError::Assembly)?;
1196 Self::jit_raw_from(resolved)
1197 }
1198
1199 #[cfg(feature = "bench-tiers")]
1224 pub fn compile_closures_raw(
1225 self,
1226 ) -> Result<crate::compile::closures::CompiledKernelRaw, KernelError> {
1227 let resolved = self.resolve_with_log(None)?;
1228 let (coord_count, total_slots, steps, output_map, ref_slots, extras) =
1229 Self::build_p2_layout(&resolved).map_err(Self::refused_by_closures)?;
1230 crate::compile::closures::CompiledKernelRaw::new(
1231 coord_count,
1232 total_slots,
1233 steps,
1234 output_map,
1235 ref_slots,
1236 extras,
1237 )
1238 }
1239
1240 #[cfg(all(feature = "bench-tiers", feature = "jit"))]
1242 pub fn compile_native_raw(
1243 self,
1244 ) -> Result<crate::compile::hybrid::HybridKernelRaw, KernelError> {
1245 let resolved = self.resolve_with_log(None)?;
1246 Ok(Self::hybrid_from(resolved)?.into_raw())
1247 }
1248
1249 #[cfg(all(feature = "bench-tiers", feature = "jit"))]
1251 pub fn compile_pure_native_raw(self) -> Result<crate::compile::jit::JitKernelRaw, KernelError> {
1252 self.try_compile_pure_jit_raw()
1253 }
1254
1255 pub(crate) fn attribution_of(resolved: &ResolvedDag) -> crate::compile::Attribution {
1260 let layout = slot_layout(resolved);
1261 let sites = resolved
1262 .nodes
1263 .iter()
1264 .enumerate()
1265 .map(|(node_idx, node)| {
1266 let mut outputs: Vec<String> = resolved
1267 .output_map
1268 .iter()
1269 .filter(|(_, (n, _))| *n == node_idx)
1270 .map(|(name, _)| name.clone())
1271 .collect();
1272 outputs.sort();
1273 let inputs = resolved.wiring[node_idx]
1274 .iter()
1275 .map(|source| match source {
1276 WireSource::Input(c) => (
1277 layout.input_starts.get(*c).copied().unwrap_or(*c),
1278 resolved
1279 .input_defs
1280 .get(*c)
1281 .map(|d| d.port_type)
1282 .unwrap_or(PortType::U64),
1283 ),
1284 WireSource::NodeOutput(u, p) => (
1285 layout.port_offsets[*u][*p],
1286 resolved.nodes[*u].meta().outs[*p].typ,
1287 ),
1288 })
1289 .collect();
1290 crate::compile::NodeSite {
1291 name: node.meta().name.to_string(),
1292 outputs,
1293 inputs,
1294 }
1295 })
1296 .collect();
1297 crate::compile::Attribution {
1298 sites,
1299 context: resolved.context.clone(),
1300 }
1301 }
1302
1303 #[cfg(feature = "jit")]
1304 fn jit_raw_from(
1305 resolved: ResolvedDag,
1306 ) -> Result<crate::compile::jit::JitKernelRaw, KernelError> {
1307 let _coord_names = resolved.input_names();
1308 let (coord_count, total_slots, jit_steps, output_map, scratch, volatile) =
1309 Self::build_jit_layout(&resolved).map_err(Self::refused_by_pure_native)?;
1310 let (guard, types) = Self::jit_slot_info(&resolved);
1311 let externs = Self::externs_of(&resolved).map_err(Self::refused_by_pure_native)?;
1312 let attribution = std::sync::Arc::new(Self::attribution_of(&resolved));
1313 let (folded, origin) = Self::constant_steps(&resolved, &jit_steps);
1314 let alone = Self::side_channels(&resolved);
1315 let mut k = crate::compile::jit::compile_jit_raw_with(
1316 coord_count,
1317 total_slots,
1318 jit_steps,
1319 output_map,
1320 resolved.nodes,
1321 externs,
1322 scratch,
1323 volatile,
1324 alone,
1325 )
1326 .map_err(Self::refused_by_pure_native)?;
1327 k.set_slot_info(guard, types);
1328 k.set_attribution(attribution);
1329 k.fold_constants(&folded, &origin, total_slots)?;
1332 Ok(k)
1333 }
1334
1335 #[cfg(feature = "jit")]
1341 #[doc(hidden)]
1342 pub fn try_compile_tier1_simd_ordinal(
1343 self,
1344 driving_input: &str,
1345 output: &str,
1346 ) -> Result<
1347 crate::compile::simd_tier1::Tier1SimdExecutor,
1348 crate::compile::simd_tier1::Tier1SimdError,
1349 > {
1350 let resolved = self.resolve().map_err(|error| {
1351 crate::compile::simd_tier1::Tier1SimdError::VectorGraphBuild(error.to_string())
1352 })?;
1353 crate::compile::simd_tier1::compile_tier1_ordinal(resolved, driving_input, output)
1354 }
1355
1356 fn hybrid_from(
1357 resolved: ResolvedDag,
1358 ) -> Result<crate::compile::hybrid::HybridKernel, KernelError> {
1359 let _coord_names = resolved.input_names();
1360 let layout = slot_layout(&resolved);
1361
1362 let output_map = layout.named_outputs(&resolved);
1363 let input_widths: Vec<usize> = resolved
1364 .input_defs
1365 .iter()
1366 .map(|d| d.port_type.slot_width())
1367 .collect();
1368
1369 let ref_slots = layout.ref_slot_mask(&resolved);
1370 let input_types: Vec<PortType> = resolved.input_defs.iter().map(|d| d.port_type).collect();
1371 let externs = Self::externs_of(&resolved).map_err(Self::refused_by_native)?;
1372 let attribution = std::sync::Arc::new(Self::attribution_of(&resolved));
1373 let classes = PolydatProgram::classify_lifecycle(
1376 &resolved.nodes,
1377 &resolved.wiring,
1378 &resolved.input_defs,
1379 &resolved.output_map,
1380 &resolved.output_modifiers,
1381 );
1382 let constant: Vec<bool> = classes
1383 .lifecycle
1384 .iter()
1385 .map(|lc| *lc == crate::kernel::EvalLifecycle::CompileConst)
1386 .collect();
1387 let mut kernel = crate::compile::hybrid::build_hybrid(
1388 &resolved.nodes,
1389 &resolved.wiring,
1390 layout.coord_slots,
1391 layout.total_slots,
1392 &layout.port_offsets,
1393 &layout.input_starts,
1394 &input_widths,
1395 output_map,
1396 ref_slots,
1397 &input_types,
1398 externs,
1399 constant,
1400 classes.nondeterministic,
1401 attribution,
1402 )?;
1403 kernel.retain_nodes(resolved.nodes);
1404 Ok(kernel)
1405 }
1406
1407 fn log_forms(resolved: &ResolvedDag, log: &mut crate::dsl::events::CompileEventLog) {
1415 for (node_idx, node) in resolved.nodes.iter().enumerate() {
1416 let wire_types = wire_types_of(resolved, node_idx);
1417 #[cfg(feature = "jit")]
1421 let native = !matches!(
1422 crate::compile::jit::classify_node_typed(node.as_ref(), &wire_types),
1423 crate::compile::jit::JitOp::Fallback
1424 );
1425 #[cfg(not(feature = "jit"))]
1426 let native = false;
1427 let level = if native {
1428 "native"
1429 } else {
1430 match node_step_op(node.as_ref(), &wire_types) {
1431 Some((crate::compile::closures::StepOp::Copy, _)) => "slot copy",
1432 Some((crate::compile::closures::StepOp::U64(_), _)) => "compiled u64 op",
1433 Some((crate::compile::closures::StepOp::Slot(_), _)) => "slot kit",
1434 None => "interpreted",
1435 }
1436 };
1437 let name = resolved
1438 .output_map
1439 .iter()
1440 .find(|(_, (ni, _))| *ni == node_idx)
1441 .map(|(n, _)| n.clone())
1442 .unwrap_or_else(|| node.meta().name.clone());
1443 log.push(crate::dsl::events::CompileEvent::CompileLevelSelected {
1444 node: name,
1445 level: level.to_string(),
1446 });
1447 }
1448 }
1449
1450 fn log_summary(
1455 log: Option<&mut crate::dsl::events::CompileEventLog>,
1456 nodes: usize,
1457 outputs: usize,
1458 ) {
1459 if let Some(log) = log {
1460 let constants_folded = log
1461 .events()
1462 .iter()
1463 .filter(|e| matches!(e, crate::dsl::events::CompileEvent::ConstantFolded { .. }))
1464 .count();
1465 log.push(crate::dsl::events::CompileEvent::Summary {
1466 nodes,
1467 outputs,
1468 constants_folded,
1469 });
1470 }
1471 }
1472
1473 #[cfg(feature = "jit")]
1476 fn resolve(self) -> Result<ResolvedDag, AssemblyError> {
1477 self.resolve_with_log(None)
1478 }
1479
1480 fn resolve_with_log(
1481 self,
1482 mut log: Option<&mut crate::dsl::events::CompileEventLog>,
1483 ) -> Result<ResolvedDag, AssemblyError> {
1484 if let Some(log) = log.as_deref_mut() {
1490 let cursor_slot = |name: &str| {
1491 self.cursor_schemas
1492 .iter()
1493 .any(|s| name.starts_with(&format!("{}__cursor", s.name)))
1494 };
1495 for def in &self.input_defs {
1496 if matches!(
1497 def.kind,
1498 crate::kernel::InputKind::ExternalWrite
1499 | crate::kernel::InputKind::IterationExtern
1500 ) && def.default == crate::ast::Value::None
1501 && !cursor_slot(&def.name)
1502 {
1503 log.push(crate::dsl::events::CompileEvent::ExternWithoutDefault {
1504 name: def.name.clone(),
1505 port_type: def.port_type.to_string(),
1506 });
1507 }
1508 }
1509 }
1510 let mut name_to_idx: HashMap<String, usize> = HashMap::new();
1512 for (i, pn) in self.nodes.iter().enumerate() {
1513 if name_to_idx.contains_key(&pn.name) {
1514 return Err(AssemblyError::DuplicateNode(pn.name.clone()));
1515 }
1516 name_to_idx.insert(pn.name.clone(), i);
1517 }
1518
1519 let input_to_idx: HashMap<String, usize> = self
1521 .input_defs
1522 .iter()
1523 .enumerate()
1524 .map(|(i, d)| (d.name.clone(), i))
1525 .collect();
1526
1527 for pn in &self.nodes {
1529 let expected = pn.node.meta().wire_inputs().len();
1530 let got = pn.inputs.len();
1531 if expected != got {
1532 return Err(AssemblyError::ArityMismatch {
1533 node_name: pn.name.clone(),
1534 expected,
1535 got,
1536 });
1537 }
1538 }
1539
1540 let mut all_nodes: Vec<PendingNode> = Vec::new();
1541 let mut all_name_to_idx: HashMap<String, usize> = HashMap::new();
1542 let mut adapter_count = 0usize;
1543 let mut assertion_count = 0usize;
1544 let strict_values = self.strict_values;
1545 let strict_types = self.strict_types;
1546 let strict = self.strict;
1547
1548 for pn in self.nodes {
1549 let idx = all_nodes.len();
1550 all_name_to_idx.insert(pn.name.clone(), idx);
1551 all_nodes.push(pn);
1552 }
1553
1554 let mut resolved_wiring: Vec<Vec<WireSource>> = Vec::new();
1555
1556 for node_idx in 0..all_nodes.len() {
1557 let mut node_wiring = Vec::new();
1558
1559 for (port_idx, wire_ref) in all_nodes[node_idx].inputs.clone().iter().enumerate() {
1560 let port = all_nodes[node_idx].node.meta().wire_inputs()[port_idx].clone();
1561 let expected_type = port.typ;
1562
1563 let (source, source_type) = match wire_ref {
1564 WireRef::Input(name) => {
1565 let input_idx = input_to_idx
1566 .get(name)
1567 .ok_or_else(|| AssemblyError::UnknownWire(name.clone()))?;
1568 let source_type = self.input_defs[*input_idx].port_type;
1569 (WireSource::Input(*input_idx), source_type)
1570 }
1571 WireRef::Node(name, out_port) => {
1572 let src_idx = all_name_to_idx
1573 .get(name)
1574 .ok_or_else(|| AssemblyError::UnknownWire(name.clone()))?;
1575 let src_type = all_nodes[*src_idx].node.meta().outs[*out_port].typ;
1576 (WireSource::NodeOutput(*src_idx, *out_port), src_type)
1577 }
1578 };
1579
1580 if port.accepts_any_type || source_type == expected_type {
1588 node_wiring.push(source);
1589 } else if let Some(adapter) = auto_adapter(source_type, expected_type) {
1590 if strict {
1591 return Err(AssemblyError::Other(format!(
1592 "strict mode: implicit type coercion {source_type} → {expected_type} \
1593 into '{}'. Use an explicit conversion function (e.g., to_f64, \
1594 to_i64, f64_to_u64).",
1595 all_nodes[node_idx].name
1596 )));
1597 }
1598 let adapter_name = format!("__adapt_{adapter_count}");
1599 adapter_count += 1;
1600 let adapter_idx = all_nodes.len();
1601
1602 if let Some(ref mut log) = log {
1603 let from_name = match wire_ref {
1604 WireRef::Input(n) => n.clone(),
1605 WireRef::Node(n, _) => n.clone(),
1606 };
1607 let to_name = all_nodes[node_idx].name.clone();
1608 log.push(if is_lossless_widening(source_type, expected_type) {
1609 crate::dsl::events::CompileEvent::TypeWidening {
1610 from: source_type.to_keyword(),
1611 to: expected_type.to_keyword(),
1612 context: format!("{from_name} → {to_name}"),
1613 }
1614 } else {
1615 crate::dsl::events::CompileEvent::TypeAdapterInserted {
1616 from_node: from_name,
1617 to_node: to_name,
1618 adapter: format!("{source_type:?}→{expected_type:?}"),
1619 }
1620 });
1621 }
1622
1623 all_name_to_idx.insert(adapter_name.clone(), adapter_idx);
1624
1625 let adapter_wiring = vec![source];
1626 while resolved_wiring.len() <= adapter_idx {
1627 resolved_wiring.push(Vec::new());
1628 }
1629 resolved_wiring[adapter_idx] = adapter_wiring;
1630
1631 all_nodes.push(PendingNode {
1632 name: adapter_name,
1633 node: adapter,
1634 inputs: vec![],
1635 });
1636
1637 node_wiring.push(WireSource::NodeOutput(adapter_idx, 0));
1638 } else {
1639 let from_name = match wire_ref {
1640 WireRef::Input(n) => n.clone(),
1641 WireRef::Node(n, _) => n.clone(),
1642 };
1643 return Err(AssemblyError::TypeMismatch {
1644 from_node: from_name,
1645 from_port: match wire_ref {
1646 WireRef::Input(_) => 0,
1647 WireRef::Node(_, p) => *p,
1648 },
1649 from_type: source_type,
1650 to_node: all_nodes[node_idx].name.clone(),
1651 to_port: port_idx,
1652 to_type: expected_type,
1653 });
1654 }
1655
1656 let sink_port = &all_nodes[node_idx].node.meta().wire_inputs()[port_idx];
1669 if let Some(constraint) = sink_port.constraint {
1670 let last_source = node_wiring.last().expect("wire just pushed").clone();
1671 if strict_values
1672 && !value_constraint_proven(&all_nodes, &last_source, &constraint)
1673 {
1674 let assert_name = format!("__assert_v_{assertion_count}");
1675 assertion_count += 1;
1676 let assert_idx = all_nodes.len();
1677
1678 if let Some(ref mut log) = log {
1679 let from_name = match wire_ref {
1680 WireRef::Input(n) => n.clone(),
1681 WireRef::Node(n, _) => n.clone(),
1682 };
1683 log.push(crate::dsl::events::CompileEvent::AssertionInserted {
1684 from_node: from_name,
1685 to_node: all_nodes[node_idx].name.clone(),
1686 kind: format!("{:?} value-assert {:?}", expected_type, constraint),
1687 });
1688 }
1689
1690 all_name_to_idx.insert(assert_name.clone(), assert_idx);
1691 let assert_wiring = vec![last_source];
1692 while resolved_wiring.len() <= assert_idx {
1693 resolved_wiring.push(Vec::new());
1694 }
1695 resolved_wiring[assert_idx] = assert_wiring;
1696
1697 all_nodes.push(PendingNode {
1698 name: assert_name,
1699 node: crate::library::assertions::assert_value_node(
1700 expected_type,
1701 constraint,
1702 ),
1703 inputs: vec![],
1704 });
1705
1706 *node_wiring.last_mut().unwrap() = WireSource::NodeOutput(assert_idx, 0);
1709 } else if let Some(ref mut log) = log {
1710 let from_name = match wire_ref {
1711 WireRef::Input(n) => n.clone(),
1712 WireRef::Node(n, _) => n.clone(),
1713 };
1714 log.push(crate::dsl::events::CompileEvent::AssertionSkipped {
1715 from_node: from_name,
1716 to_node: all_nodes[node_idx].name.clone(),
1717 reason: assertion_skip_reason(
1718 strict_values,
1719 &all_nodes,
1720 &last_source,
1721 &constraint,
1722 ),
1723 });
1724 }
1725 } else if strict_types && source_type != expected_type {
1726 }
1734 }
1735
1736 while resolved_wiring.len() <= node_idx {
1737 resolved_wiring.push(Vec::new());
1738 }
1739 resolved_wiring[node_idx] = node_wiring;
1740 }
1741
1742 while resolved_wiring.len() < all_nodes.len() {
1743 resolved_wiring.push(Vec::new());
1744 }
1745
1746 {
1751 let rules = crate::compile::fusion::default_rules();
1752 if !rules.is_empty() {
1753 let mut output_nodes: Vec<usize> = Vec::new();
1756 for wire_ref in self.outputs.values() {
1757 if let WireRef::Node(node_name, _) = wire_ref
1758 && let Some(&idx) = all_name_to_idx.get(node_name)
1759 {
1760 output_nodes.push(idx);
1761 }
1762 }
1763
1764 let mut opt_nodes: Vec<Option<Box<dyn PolydatNode>>> =
1766 all_nodes.into_iter().map(|pn| Some(pn.node)).collect();
1767
1768 let fused_count = crate::compile::fusion::apply_fusions(
1769 &mut opt_nodes,
1770 &mut resolved_wiring,
1771 &mut all_name_to_idx,
1772 &rules,
1773 &output_nodes,
1774 );
1775 if fused_count > 0
1776 && let Some(ref mut log) = log
1777 {
1778 log.push(crate::dsl::events::CompileEvent::FusionApplied {
1779 pattern: "subgraph".into(),
1780 nodes_replaced: fused_count,
1781 });
1782 }
1783
1784 all_nodes = opt_nodes
1787 .into_iter()
1788 .enumerate()
1789 .map(|(i, opt)| PendingNode {
1790 name: all_name_to_idx
1791 .iter()
1792 .find(|&(_, &idx)| idx == i)
1793 .map(|(n, _)| n.clone())
1794 .unwrap_or_else(|| format!("__removed_{i}")),
1795 node: opt.unwrap_or_else(|| {
1796 Box::new(crate::library::identity::Identity::new(
1797 crate::ast::PortType::U64,
1798 ))
1799 }),
1800 inputs: vec![], })
1802 .collect();
1803 }
1804 }
1805
1806 let node_count = all_nodes.len();
1813 let mut reachable = vec![false; node_count];
1814 {
1815 let mut worklist: Vec<usize> = Vec::new();
1816 for wire_ref in self.outputs.values() {
1818 if let WireRef::Node(node_name, _) = wire_ref
1819 && let Some(&idx) = all_name_to_idx.get(node_name)
1820 {
1821 worklist.push(idx);
1822 }
1823 }
1824 for (idx, pn) in all_nodes.iter().enumerate() {
1834 if matches!(
1835 pn.node.meta().name.as_str(),
1836 "log_debug" | "log_info" | "log_warn" | "log_error"
1837 ) {
1838 worklist.push(idx);
1839 }
1840 }
1841 while let Some(idx) = worklist.pop() {
1843 if reachable[idx] {
1844 continue;
1845 }
1846 reachable[idx] = true;
1847 for source in &resolved_wiring[idx] {
1848 if let WireSource::NodeOutput(upstream, _) = source
1849 && !reachable[*upstream]
1850 {
1851 worklist.push(*upstream);
1852 }
1853 }
1854 }
1855 }
1856 let live_count = reachable.iter().filter(|&&r| r).count();
1857
1858 let mut in_degree = vec![0usize; node_count];
1860 let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); node_count];
1861
1862 for (node_idx, wiring) in resolved_wiring.iter().enumerate() {
1863 if !reachable[node_idx] {
1864 continue;
1865 }
1866 for source in wiring {
1867 if let WireSource::NodeOutput(upstream, _) = source {
1868 in_degree[node_idx] += 1;
1869 dependents[*upstream].push(node_idx);
1870 }
1871 }
1872 }
1873
1874 let mut queue: Vec<usize> = (0..node_count)
1875 .filter(|i| reachable[*i] && in_degree[*i] == 0)
1876 .collect();
1877 let mut sorted_order: Vec<usize> = Vec::with_capacity(live_count);
1878
1879 while let Some(idx) = queue.pop() {
1880 sorted_order.push(idx);
1881 for &dep in &dependents[idx] {
1882 in_degree[dep] -= 1;
1883 if in_degree[dep] == 0 {
1884 queue.push(dep);
1885 }
1886 }
1887 }
1888
1889 if sorted_order.len() != live_count {
1890 return Err(AssemblyError::CycleDetected);
1891 }
1892
1893 let mut old_to_new = vec![0usize; node_count];
1894 for (new_idx, &old_idx) in sorted_order.iter().enumerate() {
1895 old_to_new[old_idx] = new_idx;
1896 }
1897
1898 let mut sorted_nodes: Vec<Option<Box<dyn PolydatNode>>> =
1899 all_nodes.into_iter().map(|pn| Some(pn.node)).collect();
1900
1901 let final_nodes: Vec<Box<dyn PolydatNode>> = sorted_order
1902 .iter()
1903 .map(|&old_idx| sorted_nodes[old_idx].take().unwrap())
1904 .collect();
1905
1906 let final_wiring: Vec<Vec<WireSource>> = sorted_order
1907 .iter()
1908 .map(|&old_idx| {
1909 resolved_wiring[old_idx]
1910 .iter()
1911 .map(|source| match source {
1912 WireSource::Input(c) => WireSource::Input(*c),
1913 WireSource::NodeOutput(old_up, port) => {
1914 WireSource::NodeOutput(old_to_new[*old_up], *port)
1915 }
1916 })
1917 .collect()
1918 })
1919 .collect();
1920
1921 let mut final_output_map: HashMap<String, (usize, usize)> = HashMap::new();
1922 for (name, wire_ref) in &self.outputs {
1923 match wire_ref {
1924 WireRef::Input(coord_name) => {
1925 return Err(AssemblyError::UnknownWire(format!(
1926 "output '{name}' references coordinate '{coord_name}' directly; \
1927 wire through a node instead"
1928 )));
1929 }
1930 WireRef::Node(node_name, port) => {
1931 let old_idx = all_name_to_idx
1932 .get(node_name)
1933 .ok_or_else(|| AssemblyError::UnknownWire(node_name.clone()))?;
1934 final_output_map.insert(name.clone(), (old_to_new[*old_idx], *port));
1935 }
1936 }
1937 }
1938
1939 for f in crate::compile::roundtrip_lint::lint_type_round_trips(
1946 &final_nodes,
1947 &final_wiring,
1948 &self.input_defs,
1949 ) {
1950 if strict_values {
1951 return Err(AssemblyError::Other(f.message()));
1952 }
1953 crate::library::support::audit::warn(&f.message());
1956 if let Some(ref mut log) = log {
1957 log.push(crate::dsl::events::CompileEvent::Warning {
1958 message: f.message(),
1959 });
1960 }
1961 }
1962
1963 if let Some(log) = log {
1964 let resolved_view = ResolvedDag {
1965 nodes: final_nodes,
1966 wiring: final_wiring,
1967 input_defs: self.input_defs,
1968 coord_count: self.coord_count,
1969 output_map: final_output_map,
1970 output_order: self.output_order,
1971 source: self.source,
1972 context: self.context,
1973 output_modifiers: self.output_modifiers,
1974 const_outputs: self.const_outputs,
1975 cursor_schemas: self.cursor_schemas,
1976 ledger: self.ledger,
1977 };
1978 Self::log_forms(&resolved_view, log);
1979 return Ok(resolved_view);
1980 }
1981 Ok(ResolvedDag {
1982 nodes: final_nodes,
1983 wiring: final_wiring,
1984 input_defs: self.input_defs,
1985 coord_count: self.coord_count,
1986 output_map: final_output_map,
1987 output_order: self.output_order,
1988 source: self.source,
1989 context: self.context,
1990 output_modifiers: self.output_modifiers,
1991 const_outputs: self.const_outputs,
1992 cursor_schemas: self.cursor_schemas,
1993 ledger: self.ledger,
1994 })
1995 }
1996}
1997
1998fn value_constraint_proven(
2013 all_nodes: &[PendingNode],
2014 src: &WireSource,
2015 _constraint: &crate::dsl::const_constraints::ConstConstraint,
2016) -> bool {
2017 match src {
2018 WireSource::Input(_) => false,
2019 WireSource::NodeOutput(idx, _) => {
2020 let meta = all_nodes[*idx].node.meta();
2021 let no_wire_inputs = meta.wire_inputs().is_empty();
2026 if no_wire_inputs {
2027 return true;
2028 }
2029 if meta.name.starts_with("__assert_v_") || meta.name.starts_with("assert_") {
2034 return true;
2035 }
2036 false
2037 }
2038 }
2039}
2040
2041fn assertion_skip_reason(
2045 strict_values: bool,
2046 all_nodes: &[PendingNode],
2047 src: &WireSource,
2048 _constraint: &crate::dsl::const_constraints::ConstConstraint,
2049) -> String {
2050 if !strict_values {
2051 return "strict_values not enabled".into();
2052 }
2053 match src {
2054 WireSource::Input(_) => "raw input wire".into(),
2055 WireSource::NodeOutput(idx, _) => {
2056 let meta = all_nodes[*idx].node.meta();
2057 if meta.wire_inputs().is_empty() {
2058 "constant source already validated".into()
2059 } else if meta.name.starts_with("__assert_v_") || meta.name.starts_with("assert_") {
2060 "upstream assertion".into()
2061 } else {
2062 "no skip rule matched".into()
2063 }
2064 }
2065 }
2066}
2067
2068pub(crate) fn shared_outputs_of(resolved: &ResolvedDag) -> Vec<&str> {
2071 let mut shared: Vec<&str> = resolved
2072 .output_modifiers
2073 .iter()
2074 .filter(|(_, m)| **m == crate::dsl::ast::BindingModifier::SHARED)
2075 .map(|(name, _)| name.as_str())
2076 .collect();
2077 shared.sort();
2078 shared
2079}
2080
2081fn is_lossless_widening(from: PortType, to: PortType) -> bool {
2087 use PortType as P;
2088 matches!(
2089 (from, to),
2090 (P::U64, P::F64)
2091 | (P::U32, P::U64)
2092 | (P::U32, P::I64)
2093 | (P::U32, P::F64)
2094 | (P::I32, P::I64)
2095 | (P::I32, P::F64)
2096 | (P::I64, P::F64)
2097 | (P::F32, P::F64)
2098 )
2099}
2100
2101pub(crate) fn wire_types_of(resolved: &ResolvedDag, node_idx: usize) -> Vec<PortType> {
2102 resolved.wiring[node_idx]
2103 .iter()
2104 .map(|src| match src {
2105 crate::kernel::WireSource::Input(i) => resolved.input_defs[*i].port_type,
2106 crate::kernel::WireSource::NodeOutput(j, p) => resolved.nodes[*j].meta().outs[*p].typ,
2107 })
2108 .collect()
2109}
2110
2111pub fn auto_adapter(from: PortType, to: PortType) -> Option<Box<dyn PolydatNode>> {
2115 use crate::library::convert::{
2116 BoolToStr, BoolToU64, F32ToF64, F32ToString, I32ToF64, I32ToI64, I32ToString, I64ToF64,
2117 I64ToString, U32ToF64, U32ToI64, U32ToString, U32ToU64,
2118 };
2119 use crate::library::polyfill as P;
2120 use crate::library::polyfill_128 as W;
2121 use crate::library::polyfill_complete as C;
2122 use crate::library::polyfill_narrow as N;
2123 match (from, to) {
2124 (PortType::U64, PortType::F64) => Some(Box::new(U64ToF64::new())),
2126 (PortType::U32, PortType::U64) => Some(Box::new(U32ToU64::new())),
2127 (PortType::U32, PortType::I64) => Some(Box::new(U32ToI64::new())),
2128 (PortType::U32, PortType::F64) => Some(Box::new(U32ToF64::new())),
2129 (PortType::I32, PortType::I64) => Some(Box::new(I32ToI64::new())),
2130 (PortType::I32, PortType::F64) => Some(Box::new(I32ToF64::new())),
2131 (PortType::I32, PortType::F32) => Some(Box::new(P::I32ToF32::new())),
2137 (PortType::I64, PortType::F64) => Some(Box::new(I64ToF64::new())),
2138 (PortType::F32, PortType::F64) => Some(Box::new(F32ToF64::new())),
2139
2140 (PortType::U64, PortType::Str) => Some(Box::new(U64ToString::new())),
2142 (PortType::F64, PortType::Str) => Some(Box::new(F64ToString::new())),
2143 (PortType::Bool, PortType::Str) => Some(Box::new(BoolToStr::new())),
2144 (PortType::Json, PortType::Str) => Some(Box::new(JsonToStr::new())),
2145 (PortType::U32, PortType::Str) => Some(Box::new(U32ToString::new())),
2146 (PortType::I32, PortType::Str) => Some(Box::new(I32ToString::new())),
2147 (PortType::I64, PortType::Str) => Some(Box::new(I64ToString::new())),
2148 (PortType::F32, PortType::Str) => Some(Box::new(F32ToString::new())),
2149
2150 (PortType::Bool, PortType::U64) => Some(Box::new(BoolToU64::new())),
2152 (PortType::Bool, PortType::U32) => Some(Box::new(P::BoolToU32::new())),
2153 (PortType::Bool, PortType::I64) => Some(Box::new(P::BoolToI64::new())),
2154 (PortType::Bool, PortType::I32) => Some(Box::new(P::BoolToI32::new())),
2155 (PortType::Bool, PortType::F64) => Some(Box::new(P::BoolToF64::new())),
2156 (PortType::Bool, PortType::F32) => Some(Box::new(P::BoolToF32::new())),
2157 (PortType::U64, PortType::Bool) => {
2158 Some(Box::new(crate::library::convert::U64ToBool::new()))
2159 }
2160 (PortType::U32, PortType::Bool) => Some(Box::new(P::U32ToBool::new())),
2161 (PortType::I64, PortType::Bool) => Some(Box::new(P::I64ToBool::new())),
2162 (PortType::I32, PortType::Bool) => Some(Box::new(P::I32ToBool::new())),
2163 (PortType::F64, PortType::Bool) => Some(Box::new(P::F64ToBool::new())),
2164 (PortType::F32, PortType::Bool) => Some(Box::new(P::F32ToBool::new())),
2165
2166 (PortType::U64, PortType::Bytes) => Some(Box::new(P::U64ToBytes::new())),
2168 (PortType::U32, PortType::Bytes) => Some(Box::new(P::U32ToBytes::new())),
2169 (PortType::I64, PortType::Bytes) => Some(Box::new(P::I64ToBytes::new())),
2170 (PortType::I32, PortType::Bytes) => Some(Box::new(P::I32ToBytes::new())),
2171 (PortType::F64, PortType::Bytes) => Some(Box::new(P::F64ToBytes::new())),
2172 (PortType::F32, PortType::Bytes) => Some(Box::new(P::F32ToBytes::new())),
2173 (PortType::Bool, PortType::Bytes) => Some(Box::new(P::BoolToBytes::new())),
2174 (PortType::VecF32, PortType::Bytes) => Some(Box::new(P::VecF32ToBytes::new())),
2175 (PortType::VecI32, PortType::Bytes) => Some(Box::new(P::VecI32ToBytes::new())),
2176
2177 (PortType::U64, PortType::Json) => Some(Box::new(P::U64ToJson::new())),
2181 (PortType::U32, PortType::Json) => Some(Box::new(P::U32ToJson::new())),
2182 (PortType::I64, PortType::Json) => Some(Box::new(P::I64ToJson::new())),
2183 (PortType::I32, PortType::Json) => Some(Box::new(P::I32ToJson::new())),
2184 (PortType::Bool, PortType::Json) => Some(Box::new(P::BoolToJson::new())),
2185 (PortType::VecI32, PortType::Json) => Some(Box::new(P::VecI32ToJson::new())),
2186
2187 (PortType::VecI32, PortType::VecF32) => Some(Box::new(P::VecI32ToVecF32::new())),
2189
2190 (PortType::U8, PortType::U64) => Some(Box::new(N::U8ToU64::new())),
2195 (PortType::U8, PortType::U32) => Some(Box::new(N::U8ToU32::new())),
2196 (PortType::U8, PortType::U16) => Some(Box::new(N::U8ToU16::new())),
2197 (PortType::U8, PortType::F64) => Some(Box::new(N::U8ToF64::new())),
2198 (PortType::U16, PortType::U64) => Some(Box::new(N::U16ToU64::new())),
2199 (PortType::U16, PortType::U32) => Some(Box::new(N::U16ToU32::new())),
2200 (PortType::U16, PortType::F64) => Some(Box::new(N::U16ToF64::new())),
2201 (PortType::I8, PortType::I64) => Some(Box::new(N::I8ToI64::new())),
2202 (PortType::I8, PortType::I32) => Some(Box::new(N::I8ToI32::new())),
2203 (PortType::I8, PortType::I16) => Some(Box::new(N::I8ToI16::new())),
2204 (PortType::I8, PortType::F64) => Some(Box::new(N::I8ToF64::new())),
2205 (PortType::I16, PortType::I64) => Some(Box::new(N::I16ToI64::new())),
2206 (PortType::I16, PortType::I32) => Some(Box::new(N::I16ToI32::new())),
2207 (PortType::I16, PortType::F64) => Some(Box::new(N::I16ToF64::new())),
2208 (PortType::F16, PortType::F32) => Some(Box::new(N::F16ToF32::new())),
2209 (PortType::F16, PortType::F64) => Some(Box::new(N::F16ToF64::new())),
2210 (PortType::U8, PortType::I16) => Some(Box::new(N::U8ToI16::new())),
2213 (PortType::U8, PortType::I32) => Some(Box::new(N::U8ToI32::new())),
2214 (PortType::U8, PortType::I64) => Some(Box::new(N::U8ToI64::new())),
2215 (PortType::U8, PortType::F32) => Some(Box::new(N::U8ToF32::new())),
2216 (PortType::U16, PortType::I32) => Some(Box::new(N::U16ToI32::new())),
2217 (PortType::U16, PortType::I64) => Some(Box::new(N::U16ToI64::new())),
2218 (PortType::U16, PortType::F32) => Some(Box::new(N::U16ToF32::new())),
2219 (PortType::I8, PortType::F32) => Some(Box::new(N::I8ToF32::new())),
2220 (PortType::I16, PortType::F32) => Some(Box::new(N::I16ToF32::new())),
2221 (PortType::U8, PortType::F16) => Some(Box::new(N::U8ToF16::new())),
2222 (PortType::I8, PortType::F16) => Some(Box::new(N::I8ToF16::new())),
2223 (PortType::U8, PortType::Str) => Some(Box::new(N::U8ToString::new())),
2224 (PortType::U16, PortType::Str) => Some(Box::new(N::U16ToString::new())),
2225 (PortType::I8, PortType::Str) => Some(Box::new(N::I8ToString::new())),
2226 (PortType::I16, PortType::Str) => Some(Box::new(N::I16ToString::new())),
2227 (PortType::F16, PortType::Str) => Some(Box::new(N::F16ToString::new())),
2228 (PortType::Bool, PortType::U8) => Some(Box::new(N::BoolToU8::new())),
2229 (PortType::Bool, PortType::U16) => Some(Box::new(N::BoolToU16::new())),
2230 (PortType::Bool, PortType::I8) => Some(Box::new(N::BoolToI8::new())),
2231 (PortType::Bool, PortType::I16) => Some(Box::new(N::BoolToI16::new())),
2232 (PortType::Bool, PortType::F16) => Some(Box::new(N::BoolToF16::new())),
2233 (PortType::U8, PortType::Bool) => Some(Box::new(N::U8ToBool::new())),
2234 (PortType::U16, PortType::Bool) => Some(Box::new(N::U16ToBool::new())),
2235 (PortType::I8, PortType::Bool) => Some(Box::new(N::I8ToBool::new())),
2236 (PortType::I16, PortType::Bool) => Some(Box::new(N::I16ToBool::new())),
2237 (PortType::F16, PortType::Bool) => Some(Box::new(N::F16ToBool::new())),
2238 (PortType::U8, PortType::Bytes) => Some(Box::new(N::U8ToBytes::new())),
2239 (PortType::U16, PortType::Bytes) => Some(Box::new(N::U16ToBytes::new())),
2240 (PortType::I8, PortType::Bytes) => Some(Box::new(N::I8ToBytes::new())),
2241 (PortType::I16, PortType::Bytes) => Some(Box::new(N::I16ToBytes::new())),
2242 (PortType::F16, PortType::Bytes) => Some(Box::new(N::F16ToBytes::new())),
2243 (PortType::U8, PortType::Json) => Some(Box::new(N::U8ToJson::new())),
2244 (PortType::U16, PortType::Json) => Some(Box::new(N::U16ToJson::new())),
2245 (PortType::I8, PortType::Json) => Some(Box::new(N::I8ToJson::new())),
2246 (PortType::I16, PortType::Json) => Some(Box::new(N::I16ToJson::new())),
2247
2248 (PortType::U64, PortType::U128) => Some(Box::new(W::U64ToU128::new())),
2253 (PortType::U64, PortType::I128) => Some(Box::new(W::U64ToI128::new())),
2254 (PortType::I64, PortType::I128) => Some(Box::new(W::I64ToI128::new())),
2255 (PortType::U8, PortType::U128) => Some(Box::new(W::U8ToU128::new())),
2260 (PortType::U8, PortType::I128) => Some(Box::new(W::U8ToI128::new())),
2261 (PortType::U16, PortType::U128) => Some(Box::new(W::U16ToU128::new())),
2262 (PortType::U16, PortType::I128) => Some(Box::new(W::U16ToI128::new())),
2263 (PortType::U32, PortType::U128) => Some(Box::new(W::U32ToU128::new())),
2264 (PortType::U32, PortType::I128) => Some(Box::new(W::U32ToI128::new())),
2265 (PortType::I8, PortType::I128) => Some(Box::new(W::I8ToI128::new())),
2266 (PortType::I16, PortType::I128) => Some(Box::new(W::I16ToI128::new())),
2267 (PortType::I32, PortType::I128) => Some(Box::new(W::I32ToI128::new())),
2268 (PortType::Bool, PortType::U128) => Some(Box::new(W::BoolToU128::new())),
2269 (PortType::Bool, PortType::I128) => Some(Box::new(W::BoolToI128::new())),
2270 (PortType::U128, PortType::Bool) => Some(Box::new(W::U128ToBool::new())),
2271 (PortType::I128, PortType::Bool) => Some(Box::new(W::I128ToBool::new())),
2272 (PortType::U128, PortType::F64) => Some(Box::new(W::U128ToF64::new())),
2273 (PortType::I128, PortType::F64) => Some(Box::new(W::I128ToF64::new())),
2274 (PortType::U128, PortType::Str) => Some(Box::new(W::U128ToString::new())),
2275 (PortType::I128, PortType::Str) => Some(Box::new(W::I128ToString::new())),
2276 (PortType::U128, PortType::Bytes) => Some(Box::new(W::U128ToBytes::new())),
2277 (PortType::I128, PortType::Bytes) => Some(Box::new(W::I128ToBytes::new())),
2278 (PortType::U128, PortType::Json) => Some(Box::new(W::U128ToJson::new())),
2279 (PortType::I128, PortType::Json) => Some(Box::new(W::I128ToJson::new())),
2280
2281 (from, to) if crate::library::register_view::is_reg_port(from) => {
2286 crate::library::register_view::reg_view(to)
2287 }
2288
2289 (PortType::VecI8, PortType::VecI16) => Some(Box::new(C::VecI8ToVecI16::new())),
2293 (PortType::VecI8, PortType::VecI32) => Some(Box::new(C::VecI8ToVecI32::new())),
2294 (PortType::VecI8, PortType::VecI64) => Some(Box::new(C::VecI8ToVecI64::new())),
2295 (PortType::VecI8, PortType::VecF16) => Some(Box::new(C::VecI8ToVecF16::new())),
2296 (PortType::VecI8, PortType::VecF32) => Some(Box::new(C::VecI8ToVecF32::new())),
2297 (PortType::VecI8, PortType::VecF64) => Some(Box::new(C::VecI8ToVecF64::new())),
2298 (PortType::VecI16, PortType::VecI32) => Some(Box::new(C::VecI16ToVecI32::new())),
2299 (PortType::VecI16, PortType::VecI64) => Some(Box::new(C::VecI16ToVecI64::new())),
2300 (PortType::VecI16, PortType::VecF32) => Some(Box::new(C::VecI16ToVecF32::new())),
2301 (PortType::VecI16, PortType::VecF64) => Some(Box::new(C::VecI16ToVecF64::new())),
2302 (PortType::VecI32, PortType::VecI64) => Some(Box::new(C::VecI32ToVecI64::new())),
2303 (PortType::VecI32, PortType::VecF64) => Some(Box::new(C::VecI32ToVecF64::new())),
2304 (PortType::VecI64, PortType::VecF64) => Some(Box::new(C::VecI64ToVecF64::new())),
2305 (PortType::VecF16, PortType::VecF32) => Some(Box::new(C::VecF16ToVecF32::new())),
2306 (PortType::VecF16, PortType::VecF64) => Some(Box::new(C::VecF16ToVecF64::new())),
2307 (PortType::VecF32, PortType::VecF64) => Some(Box::new(C::VecF32ToVecF64::new())),
2308 (PortType::VecF64, PortType::Bytes) => Some(Box::new(C::VecF64ToBytes::new())),
2309 (PortType::VecI64, PortType::Bytes) => Some(Box::new(C::VecI64ToBytes::new())),
2310 (PortType::VecF16, PortType::Bytes) => Some(Box::new(C::VecF16ToBytes::new())),
2311 (PortType::VecI16, PortType::Bytes) => Some(Box::new(C::VecI16ToBytes::new())),
2312 (PortType::VecI8, PortType::Bytes) => Some(Box::new(C::VecI8ToBytes::new())),
2313 (PortType::VecI64, PortType::Json) => Some(Box::new(C::VecI64ToJson::new())),
2314 (PortType::VecI16, PortType::Json) => Some(Box::new(C::VecI16ToJson::new())),
2315 (PortType::VecI8, PortType::Json) => Some(Box::new(C::VecI8ToJson::new())),
2316 (PortType::VecI32, PortType::Str) => Some(Box::new(P::VecI32ToStr::new())),
2317 (PortType::VecI64, PortType::Str) => Some(Box::new(C::VecI64ToStr::new())),
2318 (PortType::VecI16, PortType::Str) => Some(Box::new(C::VecI16ToStr::new())),
2319 (PortType::VecI8, PortType::Str) => Some(Box::new(C::VecI8ToStr::new())),
2320
2321 _ => None,
2322 }
2323}
2324
2325pub fn boundary_adapter(from: PortType, to: PortType) -> Option<Box<dyn PolydatNode>> {
2355 if let Some(adapter) = auto_adapter(from, to) {
2356 return Some(adapter);
2357 }
2358 use crate::library::convert::{StrToBool, StrToF64, StrToU64};
2359 use crate::library::polyfill as P;
2360 use crate::library::polyfill_128 as W;
2361 use crate::library::polyfill_complete as C;
2362 use crate::library::polyfill_narrow as N;
2363 match (from, to) {
2364 (PortType::U64, PortType::U32) => Some(Box::new(P::U64ToU32::new())),
2366 (PortType::U64, PortType::I64) => Some(Box::new(P::U64ToI64::new())),
2367 (PortType::U64, PortType::I32) => Some(Box::new(P::U64ToI32::new())),
2368 (PortType::U64, PortType::F32) => Some(Box::new(P::U64ToF32::new())),
2369 (PortType::U32, PortType::I32) => Some(Box::new(P::U32ToI32::new())),
2370 (PortType::U32, PortType::F32) => Some(Box::new(P::U32ToF32::new())),
2371 (PortType::I64, PortType::U64) => Some(Box::new(P::I64ToU64::new())),
2372 (PortType::I64, PortType::U32) => Some(Box::new(P::I64ToU32::new())),
2373 (PortType::I64, PortType::I32) => Some(Box::new(P::I64ToI32::new())),
2374 (PortType::I64, PortType::F32) => Some(Box::new(P::I64ToF32::new())),
2375 (PortType::I32, PortType::U64) => Some(Box::new(P::I32ToU64::new())),
2376 (PortType::I32, PortType::U32) => Some(Box::new(P::I32ToU32::new())),
2377 (PortType::I32, PortType::F32) => Some(Box::new(P::I32ToF32::new())),
2378 (PortType::F64, PortType::U64) => Some(Box::new(P::F64ToU64Checked::new())),
2379 (PortType::F64, PortType::U32) => Some(Box::new(P::F64ToU32::new())),
2380 (PortType::F64, PortType::I64) => Some(Box::new(P::F64ToI64::new())),
2381 (PortType::F64, PortType::I32) => Some(Box::new(P::F64ToI32::new())),
2382 (PortType::F64, PortType::F32) => Some(Box::new(P::F64ToF32::new())),
2383 (PortType::F32, PortType::U64) => Some(Box::new(P::F32ToU64::new())),
2384 (PortType::F32, PortType::U32) => Some(Box::new(P::F32ToU32::new())),
2385 (PortType::F32, PortType::I64) => Some(Box::new(P::F32ToI64::new())),
2386 (PortType::F32, PortType::I32) => Some(Box::new(P::F32ToI32::new())),
2387
2388 (PortType::Str, PortType::Bool) => Some(Box::new(StrToBool::new())),
2390 (PortType::Str, PortType::U64) => Some(Box::new(StrToU64::new())),
2391 (PortType::Str, PortType::F64) => Some(Box::new(StrToF64::new())),
2392 (PortType::Str, PortType::U32) => Some(Box::new(P::StrToU32::new())),
2393 (PortType::Str, PortType::I64) => Some(Box::new(P::StrToI64::new())),
2394 (PortType::Str, PortType::I32) => Some(Box::new(P::StrToI32::new())),
2395 (PortType::Str, PortType::F32) => Some(Box::new(P::StrToF32::new())),
2396 (PortType::Str, PortType::Bytes) => Some(Box::new(P::StrToBytes::new())),
2397 (PortType::Str, PortType::Json) => Some(Box::new(P::StrToJson::new())),
2398 (PortType::Str, PortType::VecF32) => Some(Box::new(P::StrToVecF32::new())),
2399 (PortType::Str, PortType::VecI32) => Some(Box::new(P::StrToVecI32::new())),
2400
2401 (PortType::Bytes, PortType::U64) => Some(Box::new(P::BytesToU64::new())),
2403 (PortType::Bytes, PortType::U32) => Some(Box::new(P::BytesToU32::new())),
2404 (PortType::Bytes, PortType::I64) => Some(Box::new(P::BytesToI64::new())),
2405 (PortType::Bytes, PortType::I32) => Some(Box::new(P::BytesToI32::new())),
2406 (PortType::Bytes, PortType::F64) => Some(Box::new(P::BytesToF64::new())),
2407 (PortType::Bytes, PortType::F32) => Some(Box::new(P::BytesToF32::new())),
2408 (PortType::Bytes, PortType::Bool) => Some(Box::new(P::BytesToBool::new())),
2409 (PortType::Bytes, PortType::Str) => Some(Box::new(P::BytesToStr::new())),
2410 (PortType::Bytes, PortType::Json) => Some(Box::new(P::BytesToJson::new())),
2411 (PortType::Bytes, PortType::VecF32) => Some(Box::new(P::BytesToVecF32::new())),
2412 (PortType::Bytes, PortType::VecI32) => Some(Box::new(P::BytesToVecI32::new())),
2413
2414 (PortType::Json, PortType::U64) => Some(Box::new(P::JsonToU64::new())),
2416 (PortType::Json, PortType::U32) => Some(Box::new(P::JsonToU32::new())),
2417 (PortType::Json, PortType::I64) => Some(Box::new(P::JsonToI64::new())),
2418 (PortType::Json, PortType::I32) => Some(Box::new(P::JsonToI32::new())),
2419 (PortType::Json, PortType::F64) => Some(Box::new(P::JsonToF64::new())),
2420 (PortType::Json, PortType::F32) => Some(Box::new(P::JsonToF32::new())),
2421 (PortType::Json, PortType::Bool) => Some(Box::new(P::JsonToBool::new())),
2422 (PortType::Json, PortType::Bytes) => Some(Box::new(P::JsonToBytes::new())),
2423 (PortType::Json, PortType::VecF32) => Some(Box::new(P::JsonToVecF32::new())),
2424 (PortType::Json, PortType::VecI32) => Some(Box::new(P::JsonToVecI32::new())),
2425
2426 (PortType::F64, PortType::Json) => Some(Box::new(P::F64ToJson::new())),
2428 (PortType::F32, PortType::Json) => Some(Box::new(P::F32ToJson::new())),
2429 (PortType::VecF32, PortType::Json) => Some(Box::new(P::VecF32ToJson::new())),
2430 (PortType::VecF32, PortType::Str) => Some(Box::new(P::VecF32ToStr::new())),
2431
2432 (PortType::VecF32, PortType::VecI32) => Some(Box::new(P::VecF32ToVecI32::new())),
2434
2435 (PortType::U64, PortType::U8) => Some(Box::new(N::U64ToU8::new())),
2439 (PortType::U32, PortType::U8) => Some(Box::new(N::U32ToU8::new())),
2440 (PortType::U16, PortType::U8) => Some(Box::new(N::U16ToU8::new())),
2441 (PortType::I64, PortType::U8) => Some(Box::new(N::I64ToU8::new())),
2442 (PortType::F64, PortType::U8) => Some(Box::new(N::F64ToU8::new())),
2443 (PortType::U64, PortType::U16) => Some(Box::new(N::U64ToU16::new())),
2444 (PortType::U32, PortType::U16) => Some(Box::new(N::U32ToU16::new())),
2445 (PortType::I64, PortType::U16) => Some(Box::new(N::I64ToU16::new())),
2446 (PortType::F64, PortType::U16) => Some(Box::new(N::F64ToU16::new())),
2447 (PortType::I64, PortType::I8) => Some(Box::new(N::I64ToI8::new())),
2448 (PortType::I32, PortType::I8) => Some(Box::new(N::I32ToI8::new())),
2449 (PortType::U64, PortType::I8) => Some(Box::new(N::U64ToI8::new())),
2450 (PortType::F64, PortType::I8) => Some(Box::new(N::F64ToI8::new())),
2451 (PortType::I64, PortType::I16) => Some(Box::new(N::I64ToI16::new())),
2452 (PortType::I32, PortType::I16) => Some(Box::new(N::I32ToI16::new())),
2453 (PortType::U64, PortType::I16) => Some(Box::new(N::U64ToI16::new())),
2454 (PortType::F64, PortType::I16) => Some(Box::new(N::F64ToI16::new())),
2455 (PortType::F64, PortType::F16) => Some(Box::new(N::F64ToF16::new())),
2456 (PortType::F32, PortType::F16) => Some(Box::new(N::F32ToF16::new())),
2457 (PortType::U64, PortType::F16) => Some(Box::new(N::U64ToF16::new())),
2458 (PortType::Str, PortType::U8) => Some(Box::new(N::StrToU8::new())),
2459 (PortType::Str, PortType::U16) => Some(Box::new(N::StrToU16::new())),
2460 (PortType::Str, PortType::I8) => Some(Box::new(N::StrToI8::new())),
2461 (PortType::Str, PortType::I16) => Some(Box::new(N::StrToI16::new())),
2462 (PortType::Str, PortType::F16) => Some(Box::new(N::StrToF16::new())),
2463 (PortType::Bytes, PortType::U8) => Some(Box::new(N::BytesToU8::new())),
2464 (PortType::Bytes, PortType::U16) => Some(Box::new(N::BytesToU16::new())),
2465 (PortType::Bytes, PortType::I8) => Some(Box::new(N::BytesToI8::new())),
2466 (PortType::Bytes, PortType::I16) => Some(Box::new(N::BytesToI16::new())),
2467 (PortType::Bytes, PortType::F16) => Some(Box::new(N::BytesToF16::new())),
2468 (PortType::Json, PortType::U8) => Some(Box::new(N::JsonToU8::new())),
2469 (PortType::Json, PortType::U16) => Some(Box::new(N::JsonToU16::new())),
2470 (PortType::Json, PortType::I8) => Some(Box::new(N::JsonToI8::new())),
2471 (PortType::Json, PortType::I16) => Some(Box::new(N::JsonToI16::new())),
2472 (PortType::Json, PortType::F16) => Some(Box::new(N::JsonToF16::new())),
2473 (PortType::F16, PortType::Json) => Some(Box::new(N::F16ToJson::new())),
2475
2476 (PortType::U128, PortType::U64) => Some(Box::new(W::U128ToU64::new())),
2478 (PortType::I128, PortType::I64) => Some(Box::new(W::I128ToI64::new())),
2479 (PortType::I64, PortType::U128) => Some(Box::new(W::I64ToU128::new())),
2480 (PortType::U128, PortType::I128) => Some(Box::new(W::U128ToI128::new())),
2481 (PortType::I128, PortType::U128) => Some(Box::new(W::I128ToU128::new())),
2482 (PortType::F64, PortType::U128) => Some(Box::new(W::F64ToU128::new())),
2483 (PortType::F64, PortType::I128) => Some(Box::new(W::F64ToI128::new())),
2484 (PortType::Str, PortType::U128) => Some(Box::new(W::StrToU128::new())),
2485 (PortType::Str, PortType::I128) => Some(Box::new(W::StrToI128::new())),
2486 (PortType::Bytes, PortType::U128) => Some(Box::new(W::BytesToU128::new())),
2487 (PortType::Bytes, PortType::I128) => Some(Box::new(W::BytesToI128::new())),
2488 (PortType::Json, PortType::U128) => Some(Box::new(W::JsonToU128::new())),
2489 (PortType::Json, PortType::I128) => Some(Box::new(W::JsonToI128::new())),
2490
2491 (PortType::U8, PortType::I8) => Some(Box::new(C::U8ToI8::new())),
2496 (PortType::I8, PortType::U8) => Some(Box::new(C::I8ToU8::new())),
2497 (PortType::I8, PortType::U16) => Some(Box::new(C::I8ToU16::new())),
2498 (PortType::I8, PortType::U32) => Some(Box::new(C::I8ToU32::new())),
2499 (PortType::I8, PortType::U64) => Some(Box::new(C::I8ToU64::new())),
2500 (PortType::I8, PortType::U128) => Some(Box::new(C::I8ToU128::new())),
2501 (PortType::U16, PortType::I8) => Some(Box::new(C::U16ToI8::new())),
2502 (PortType::U16, PortType::I16) => Some(Box::new(C::U16ToI16::new())),
2503 (PortType::U16, PortType::F16) => Some(Box::new(C::U16ToF16::new())),
2504 (PortType::I16, PortType::U8) => Some(Box::new(C::I16ToU8::new())),
2505 (PortType::I16, PortType::I8) => Some(Box::new(C::I16ToI8::new())),
2506 (PortType::I16, PortType::U16) => Some(Box::new(C::I16ToU16::new())),
2507 (PortType::I16, PortType::F16) => Some(Box::new(C::I16ToF16::new())),
2508 (PortType::I16, PortType::U32) => Some(Box::new(C::I16ToU32::new())),
2509 (PortType::I16, PortType::U64) => Some(Box::new(C::I16ToU64::new())),
2510 (PortType::I16, PortType::U128) => Some(Box::new(C::I16ToU128::new())),
2511 (PortType::U32, PortType::I8) => Some(Box::new(C::U32ToI8::new())),
2512 (PortType::U32, PortType::I16) => Some(Box::new(C::U32ToI16::new())),
2513 (PortType::U32, PortType::F16) => Some(Box::new(C::U32ToF16::new())),
2514 (PortType::I32, PortType::U8) => Some(Box::new(C::I32ToU8::new())),
2515 (PortType::I32, PortType::U16) => Some(Box::new(C::I32ToU16::new())),
2516 (PortType::I32, PortType::F16) => Some(Box::new(C::I32ToF16::new())),
2517 (PortType::I32, PortType::U128) => Some(Box::new(C::I32ToU128::new())),
2518 (PortType::F16, PortType::U8) => Some(Box::new(C::F16ToU8::new())),
2519 (PortType::F16, PortType::I8) => Some(Box::new(C::F16ToI8::new())),
2520 (PortType::F16, PortType::U16) => Some(Box::new(C::F16ToU16::new())),
2521 (PortType::F16, PortType::I16) => Some(Box::new(C::F16ToI16::new())),
2522 (PortType::F16, PortType::U32) => Some(Box::new(C::F16ToU32::new())),
2523 (PortType::F16, PortType::I32) => Some(Box::new(C::F16ToI32::new())),
2524 (PortType::F16, PortType::U64) => Some(Box::new(C::F16ToU64::new())),
2525 (PortType::F16, PortType::I64) => Some(Box::new(C::F16ToI64::new())),
2526 (PortType::F16, PortType::U128) => Some(Box::new(C::F16ToU128::new())),
2527 (PortType::F16, PortType::I128) => Some(Box::new(C::F16ToI128::new())),
2528 (PortType::F32, PortType::U8) => Some(Box::new(C::F32ToU8::new())),
2529 (PortType::F32, PortType::I8) => Some(Box::new(C::F32ToI8::new())),
2530 (PortType::F32, PortType::U16) => Some(Box::new(C::F32ToU16::new())),
2531 (PortType::F32, PortType::I16) => Some(Box::new(C::F32ToI16::new())),
2532 (PortType::F32, PortType::U128) => Some(Box::new(C::F32ToU128::new())),
2533 (PortType::F32, PortType::I128) => Some(Box::new(C::F32ToI128::new())),
2534 (PortType::I64, PortType::F16) => Some(Box::new(C::I64ToF16::new())),
2535 (PortType::U128, PortType::U8) => Some(Box::new(C::U128ToU8::new())),
2536 (PortType::U128, PortType::I8) => Some(Box::new(C::U128ToI8::new())),
2537 (PortType::U128, PortType::U16) => Some(Box::new(C::U128ToU16::new())),
2538 (PortType::U128, PortType::I16) => Some(Box::new(C::U128ToI16::new())),
2539 (PortType::U128, PortType::F16) => Some(Box::new(C::U128ToF16::new())),
2540 (PortType::U128, PortType::U32) => Some(Box::new(C::U128ToU32::new())),
2541 (PortType::U128, PortType::I32) => Some(Box::new(C::U128ToI32::new())),
2542 (PortType::U128, PortType::F32) => Some(Box::new(C::U128ToF32::new())),
2543 (PortType::U128, PortType::I64) => Some(Box::new(C::U128ToI64::new())),
2544 (PortType::I128, PortType::U8) => Some(Box::new(C::I128ToU8::new())),
2545 (PortType::I128, PortType::I8) => Some(Box::new(C::I128ToI8::new())),
2546 (PortType::I128, PortType::U16) => Some(Box::new(C::I128ToU16::new())),
2547 (PortType::I128, PortType::I16) => Some(Box::new(C::I128ToI16::new())),
2548 (PortType::I128, PortType::F16) => Some(Box::new(C::I128ToF16::new())),
2549 (PortType::I128, PortType::U32) => Some(Box::new(C::I128ToU32::new())),
2550 (PortType::I128, PortType::I32) => Some(Box::new(C::I128ToI32::new())),
2551 (PortType::I128, PortType::F32) => Some(Box::new(C::I128ToF32::new())),
2552 (PortType::I128, PortType::U64) => Some(Box::new(C::I128ToU64::new())),
2553
2554 (PortType::VecI16, PortType::VecI8) => Some(Box::new(C::VecI16ToVecI8::new())),
2558 (PortType::VecI16, PortType::VecF16) => Some(Box::new(C::VecI16ToVecF16::new())),
2559 (PortType::VecI32, PortType::VecI8) => Some(Box::new(C::VecI32ToVecI8::new())),
2560 (PortType::VecI32, PortType::VecI16) => Some(Box::new(C::VecI32ToVecI16::new())),
2561 (PortType::VecI32, PortType::VecF16) => Some(Box::new(C::VecI32ToVecF16::new())),
2562 (PortType::VecI64, PortType::VecI8) => Some(Box::new(C::VecI64ToVecI8::new())),
2563 (PortType::VecI64, PortType::VecI16) => Some(Box::new(C::VecI64ToVecI16::new())),
2564 (PortType::VecI64, PortType::VecI32) => Some(Box::new(C::VecI64ToVecI32::new())),
2565 (PortType::VecI64, PortType::VecF16) => Some(Box::new(C::VecI64ToVecF16::new())),
2566 (PortType::VecI64, PortType::VecF32) => Some(Box::new(C::VecI64ToVecF32::new())),
2567 (PortType::VecF16, PortType::VecI8) => Some(Box::new(C::VecF16ToVecI8::new())),
2568 (PortType::VecF16, PortType::VecI16) => Some(Box::new(C::VecF16ToVecI16::new())),
2569 (PortType::VecF16, PortType::VecI32) => Some(Box::new(C::VecF16ToVecI32::new())),
2570 (PortType::VecF16, PortType::VecI64) => Some(Box::new(C::VecF16ToVecI64::new())),
2571 (PortType::VecF32, PortType::VecI8) => Some(Box::new(C::VecF32ToVecI8::new())),
2572 (PortType::VecF32, PortType::VecI16) => Some(Box::new(C::VecF32ToVecI16::new())),
2573 (PortType::VecF32, PortType::VecI64) => Some(Box::new(C::VecF32ToVecI64::new())),
2574 (PortType::VecF32, PortType::VecF16) => Some(Box::new(C::VecF32ToVecF16::new())),
2575 (PortType::VecF64, PortType::VecI8) => Some(Box::new(C::VecF64ToVecI8::new())),
2576 (PortType::VecF64, PortType::VecI16) => Some(Box::new(C::VecF64ToVecI16::new())),
2577 (PortType::VecF64, PortType::VecI32) => Some(Box::new(C::VecF64ToVecI32::new())),
2578 (PortType::VecF64, PortType::VecI64) => Some(Box::new(C::VecF64ToVecI64::new())),
2579 (PortType::VecF64, PortType::VecF16) => Some(Box::new(C::VecF64ToVecF16::new())),
2580 (PortType::VecF64, PortType::VecF32) => Some(Box::new(C::VecF64ToVecF32::new())),
2581 (PortType::Bytes, PortType::VecF64) => Some(Box::new(C::BytesToVecF64::new())),
2582 (PortType::Bytes, PortType::VecI64) => Some(Box::new(C::BytesToVecI64::new())),
2583 (PortType::Bytes, PortType::VecF16) => Some(Box::new(C::BytesToVecF16::new())),
2584 (PortType::Bytes, PortType::VecI16) => Some(Box::new(C::BytesToVecI16::new())),
2585 (PortType::Bytes, PortType::VecI8) => Some(Box::new(C::BytesToVecI8::new())),
2586 (PortType::VecF64, PortType::Json) => Some(Box::new(C::VecF64ToJson::new())),
2587 (PortType::VecF16, PortType::Json) => Some(Box::new(C::VecF16ToJson::new())),
2588 (PortType::Json, PortType::VecF64) => Some(Box::new(C::JsonToVecF64::new())),
2589 (PortType::Json, PortType::VecI64) => Some(Box::new(C::JsonToVecI64::new())),
2590 (PortType::Json, PortType::VecF16) => Some(Box::new(C::JsonToVecF16::new())),
2591 (PortType::Json, PortType::VecI16) => Some(Box::new(C::JsonToVecI16::new())),
2592 (PortType::Json, PortType::VecI8) => Some(Box::new(C::JsonToVecI8::new())),
2593 (PortType::VecF64, PortType::Str) => Some(Box::new(C::VecF64ToStr::new())),
2594 (PortType::VecF16, PortType::Str) => Some(Box::new(C::VecF16ToStr::new())),
2595 (PortType::Str, PortType::VecF64) => Some(Box::new(C::StrToVecF64::new())),
2596 (PortType::Str, PortType::VecI64) => Some(Box::new(C::StrToVecI64::new())),
2597 (PortType::Str, PortType::VecF16) => Some(Box::new(C::StrToVecF16::new())),
2598 (PortType::Str, PortType::VecI16) => Some(Box::new(C::StrToVecI16::new())),
2599 (PortType::Str, PortType::VecI8) => Some(Box::new(C::StrToVecI8::new())),
2600
2601 _ => None,
2602 }
2603}
2604
2605use crate::compile::select::{Engine, KernelError, Provenance};
2608use crate::kernel::Kernel;
2609
2610impl PolydatAssembler {
2611 pub fn compile_with(self, engine: Engine) -> Result<Box<dyn Kernel>, KernelError> {
2619 self.compile_engine_with_log(engine, None)
2620 }
2621
2622 pub fn compile_kernel(self) -> Result<Box<dyn Kernel>, KernelError> {
2625 self.compile_with(Engine::default())
2626 }
2627
2628 pub fn compile_engine_with_log(
2631 self,
2632 engine: Engine,
2633 log: Option<&mut crate::dsl::events::CompileEventLog>,
2634 ) -> Result<Box<dyn Kernel>, KernelError> {
2635 match engine {
2636 Engine::Interpreter(cones) => {
2639 let mut asm = self;
2640 asm.jit_mode = Some(cones);
2641 Ok(Box::new(asm.compile_with_log(log)?))
2642 }
2643 _ => Ok(self.compile_slots_with_log(engine, log)?),
2647 }
2648 }
2649
2650 pub fn compile_slots(
2666 self,
2667 engine: Engine,
2668 ) -> Result<Box<dyn crate::compile::SlotKernel>, KernelError> {
2669 self.compile_slots_with_log(engine, None)
2670 }
2671
2672 pub fn compile_slots_with_log(
2674 self,
2675 engine: Engine,
2676 mut log: Option<&mut crate::dsl::events::CompileEventLog>,
2677 ) -> Result<Box<dyn crate::compile::SlotKernel>, KernelError> {
2678 let refused = |reason: String| KernelError::Refused { engine, reason };
2679 let asked = |e: KernelError| match e {
2684 KernelError::Refused { reason, .. } => KernelError::Refused { engine, reason },
2685 other => other,
2686 };
2687 let strict = self.strict;
2688 match engine {
2689 Engine::Interpreter(_) => Err(refused(
2690 "the interpreter has no slot buffer: its buffers are typed `Value`s, so \
2691 there is no slot to name. Ask for `closures`, `native` or `pure-native` \
2692 for the slot surface, or compile with `compile_with` and drive the \
2693 kernel through the `Kernel` trait, which every engine answers."
2694 .into(),
2695 )),
2696 Engine::Closures(prov) => {
2697 let resolved = self.resolve_with_log(log.as_deref_mut())?;
2698 if strict {
2699 Self::refuse_strict(&resolved)?;
2700 }
2701 let folded = log.is_some().then(|| Self::constant_sites(&resolved));
2702 let (node_total, output_total) =
2703 (resolved.nodes.len(), resolved.output_order.len());
2704 let kernel = Self::closures_from(resolved, prov).map_err(asked)?;
2705 Self::log_folded(kernel.as_ref(), folded, log.as_deref_mut());
2706 Self::log_summary(log, node_total, output_total);
2707 Ok(kernel)
2708 }
2709 Engine::Native(prov) => {
2718 {
2719 let resolved = self.resolve_with_log(log.as_deref_mut())?;
2720 if strict {
2721 Self::refuse_strict(&resolved)?;
2722 }
2723 let folded = log.is_some().then(|| Self::constant_sites(&resolved));
2724 let (node_total, output_total) =
2725 (resolved.nodes.len(), resolved.output_order.len());
2726 if prov == Provenance::Push {
2732 return Err(refused(
2733 "native code has no push-only kernel: push-side invalidation \
2734 without the cone guard has no native form. Ask for `pushpull` \
2735 for both, `pull` for the guard alone, or `auto` to let the \
2736 selector choose; `push` alone is available on the closure tier."
2737 .into(),
2738 ));
2739 }
2740 let prov = Self::provenance_for(prov, &resolved);
2741 let kernel = Self::hybrid_from(resolved).map_err(asked)?;
2742 let kernel: Box<dyn crate::compile::SlotKernel> = match prov {
2743 Provenance::Raw => Box::new(kernel.into_raw()),
2744 Provenance::Pull => Box::new(kernel.into_pull()),
2745 Provenance::PushPull | Provenance::Auto => Box::new(kernel),
2751 Provenance::Push => {
2752 return Err(refused("native code has no push-only kernel".into()));
2753 }
2754 };
2755 Self::log_folded(kernel.as_ref(), folded, log.as_deref_mut());
2756 Self::log_summary(log, node_total, output_total);
2757 Ok(kernel)
2758 }
2759 }
2760 Engine::PureNative(prov) => {
2761 #[cfg(feature = "jit")]
2762 {
2763 let resolved = self.resolve_with_log(log.as_deref_mut())?;
2764 if strict {
2765 Self::refuse_strict(&resolved)?;
2766 }
2767 let folded = log.is_some().then(|| Self::constant_sites(&resolved));
2768 let (node_total, output_total) =
2769 (resolved.nodes.len(), resolved.output_order.len());
2770 let prov = match prov {
2777 Provenance::Auto => match Self::provenance_for(prov, &resolved) {
2778 Provenance::Raw => Provenance::Raw,
2779 _ => Provenance::PushPull,
2780 },
2781 named @ (Provenance::Raw | Provenance::PushPull) => named,
2782 other => {
2783 return Err(refused(format!(
2784 "pure native code has no {} kernel: the tier keeps only the \
2785 two forms the differential needs. Ask for `raw` or \
2786 `pushpull`, or `auto` to let the selector choose; every \
2787 mode is available on `native`.",
2788 format!("{other:?}").to_lowercase(),
2789 )));
2790 }
2791 };
2792 let kernel: Box<dyn crate::compile::SlotKernel> = match prov {
2793 Provenance::Raw => Box::new(Self::jit_raw_from(resolved).map_err(asked)?),
2794 _ => Box::new(Self::jit_push_pull_from(resolved).map_err(asked)?),
2795 };
2796 Self::log_folded(kernel.as_ref(), folded, log.as_deref_mut());
2797 Self::log_summary(log, node_total, output_total);
2798 Ok(kernel)
2799 }
2800 #[cfg(not(feature = "jit"))]
2801 {
2802 let _ = (prov, log);
2803 Err(refused(
2804 "this build has no native code (the `jit` feature is off)".into(),
2805 ))
2806 }
2807 }
2808 }
2809 }
2810
2811 fn constant_sites(resolved: &ResolvedDag) -> Vec<(String, usize, crate::ast::PortType)> {
2816 let classes = PolydatProgram::classify_lifecycle(
2817 &resolved.nodes,
2818 &resolved.wiring,
2819 &resolved.input_defs,
2820 &resolved.output_map,
2821 &resolved.output_modifiers,
2822 );
2823 let layout = slot_layout(resolved);
2824 resolved
2825 .nodes
2826 .iter()
2827 .enumerate()
2828 .filter(|(i, n)| {
2829 classes.lifecycle[*i] == crate::kernel::EvalLifecycle::CompileConst
2830 && n.meta().outs.len() == 1
2831 })
2832 .map(|(i, n)| {
2833 (
2834 n.meta().name.clone(),
2835 layout.port_offsets[i][0],
2836 n.meta().outs[0].typ,
2837 )
2838 })
2839 .collect()
2840 }
2841
2842 fn log_folded(
2845 kernel: &dyn Kernel,
2846 sites: Option<Vec<(String, usize, crate::ast::PortType)>>,
2847 log: Option<&mut crate::dsl::events::CompileEventLog>,
2848 ) {
2849 let (Some(sites), Some(log)) = (sites, log) else {
2850 return;
2851 };
2852 for (node, slot, ty) in sites {
2853 let value = crate::kernel::KernelInternals::slot_value(kernel, slot, ty);
2854 if !matches!(value, crate::ast::Value::None) {
2855 log.push(crate::dsl::events::CompileEvent::ConstantFolded {
2856 node,
2857 value: value.to_display_string(),
2858 });
2859 }
2860 }
2861 }
2862
2863 fn provenance_for(prov: Provenance, resolved: &ResolvedDag) -> Provenance {
2868 match prov {
2869 Provenance::Auto => {
2870 let analysis =
2871 select::analyze_graph(&resolved.nodes, &resolved.wiring, &resolved.output_map);
2872 match select::select_prov_mode(&analysis) {
2873 ProvMode::Raw => Provenance::Raw,
2874 ProvMode::Pull => Provenance::Pull,
2875 ProvMode::PushPull => Provenance::PushPull,
2876 }
2877 }
2878 p => p,
2879 }
2880 }
2881
2882 fn closures_from(
2885 resolved: ResolvedDag,
2886 prov: Provenance,
2887 ) -> Result<Box<dyn crate::compile::SlotKernel>, KernelError> {
2888 let prov = Self::provenance_for(prov, &resolved);
2889 let (coord_count, total_slots, steps, output_map, ref_slots, extras) =
2890 Self::build_p2_layout(&resolved).map_err(Self::refused_by_closures)?;
2891 let dependents = || {
2892 slot_layout(&resolved).expand_dependents(
2893 &resolved,
2894 &PolydatProgram::compute_dependents(
2895 &PolydatProgram::compute_provenance(&resolved.nodes, &resolved.wiring),
2896 resolved.input_defs.len(),
2897 ),
2898 )
2899 };
2900 Ok(match prov {
2901 Provenance::Raw => Box::new(CompiledKernelRaw::new(
2902 coord_count,
2903 total_slots,
2904 steps,
2905 output_map,
2906 ref_slots,
2907 extras,
2908 )?),
2909 Provenance::Push => Box::new(CompiledKernelPush::new(
2910 coord_count,
2911 total_slots,
2912 steps,
2913 output_map,
2914 dependents(),
2915 ref_slots,
2916 extras,
2917 )?),
2918 Provenance::Pull => Box::new(CompiledKernelPull::new(
2919 coord_count,
2920 total_slots,
2921 steps,
2922 output_map,
2923 &dependents(),
2924 ref_slots,
2925 extras,
2926 )?),
2927 Provenance::PushPull | Provenance::Auto => Box::new(CompiledKernelPushPull::new(
2928 coord_count,
2929 total_slots,
2930 steps,
2931 output_map,
2932 dependents(),
2933 ref_slots,
2934 extras,
2935 )?),
2936 })
2937 }
2938}