1use std::collections::HashMap;
25
26use crate::ast::SlotShape;
27use crate::ast::{CompiledU64Op, PolydatNode};
28use crate::kernel::WireSource;
29
30#[cfg(feature = "jit")]
31use crate::compile::jit::{self, JitOp};
32
33enum HybridStep {
35 #[cfg(feature = "jit")]
38 Jit(JitSegment),
39 Closure(ClosureStep),
41}
42
43#[cfg(feature = "jit")]
44struct JitSegment {
45 code_fn: crate::compile::jit::NativeFn,
46 _module: crate::compile::jit::JitCode,
49 fallible: bool,
52 input_slots: Vec<usize>,
55 output_slots: Vec<usize>,
56 nodes: Vec<usize>,
59}
60
61impl HybridStep {
62 fn input_slots(&self) -> &[usize] {
63 match self {
64 #[cfg(feature = "jit")]
65 HybridStep::Jit(seg) => &seg.input_slots,
66 HybridStep::Closure(cs) => &cs.input_slots,
67 }
68 }
69 fn output_slots(&self) -> &[usize] {
70 match self {
71 #[cfg(feature = "jit")]
72 HybridStep::Jit(seg) => &seg.output_slots,
73 HybridStep::Closure(cs) => &cs.output_slots,
74 }
75 }
76 fn accepts_none(&self) -> bool {
79 match self {
80 #[cfg(feature = "jit")]
81 HybridStep::Jit(_) => false,
82 HybridStep::Closure(cs) => cs.accepts_none,
83 }
84 }
85
86 #[cfg_attr(not(feature = "jit"), allow(unused_variables))]
89 fn failing_node(&self, buffer: &[u64], tracker: usize) -> usize {
90 match self {
91 #[cfg(feature = "jit")]
92 HybridStep::Jit(seg) => seg
93 .nodes
94 .get(buffer[tracker] as usize)
95 .copied()
96 .unwrap_or(usize::MAX),
97 HybridStep::Closure(cs) => cs.node,
98 }
99 }
100}
101
102enum ClosureOp {
106 U64(CompiledU64Op),
107 Slot(crate::ast::CompiledSlotOp),
108}
109
110struct ClosureStep {
111 op: ClosureOp,
112 input_slots: Vec<usize>,
113 output_slots: Vec<usize>,
114 scratch_range: (usize, usize),
116 accepts_none: bool,
118 node: usize,
120}
121
122type ResolvedOutput = (usize, crate::ast::PortType, Option<std::sync::Arc<[usize]>>);
125
126struct HybridCore {
132 buffer: Vec<u64>,
133 coord_count: usize,
134 steps: std::sync::Arc<Vec<HybridStep>>,
135 output_map: HashMap<String, usize>,
136 gather_buf: Vec<u64>,
137 scatter_buf: Vec<u64>,
138 scratch: Vec<crate::ast::ScratchBuf>,
142 ref_slots: Vec<bool>,
144 ref_scratch: Vec<(usize, usize)>,
146 output_types: HashMap<String, crate::ast::PortType>,
148 externs: crate::compile::externs::Externs,
150 traversals: std::sync::Arc<[crate::dsl::traversal::Traversal]>,
153 resolved_outputs: Vec<Option<ResolvedOutput>>,
156 _nodes: std::sync::Arc<Vec<Box<dyn PolydatNode>>>,
158 drive: crate::compile::Drive,
162 none: Vec<bool>,
164 ran: Vec<u64>,
167 epoch: u64,
173 all_ran: bool,
175 clean: Vec<bool>,
179 use_clean: bool,
181 plan: std::sync::Arc<crate::compile::Invalidation>,
184 volatile: std::sync::Arc<[bool]>,
186 side: std::sync::Arc<[bool]>,
188 slot_step: std::sync::Arc<[Option<usize>]>,
190 sites: std::sync::Arc<crate::compile::Attribution>,
192 cur_step: usize,
194 tracker: usize,
197 all: std::sync::Arc<[usize]>,
199 dirty: std::sync::Arc<[Vec<usize>]>,
205 any_none: bool,
209 volatile_steps: std::sync::Arc<[usize]>,
211}
212
213impl Clone for HybridCore {
214 fn clone(&self) -> Self {
215 let mut core = HybridCore {
216 buffer: self.buffer.clone(),
217 coord_count: self.coord_count,
218 steps: self.steps.clone(),
219 output_map: self.output_map.clone(),
220 gather_buf: self.gather_buf.clone(),
221 scatter_buf: self.scatter_buf.clone(),
222 scratch: self.scratch.clone(),
223 ref_slots: self.ref_slots.clone(),
224 ref_scratch: self.ref_scratch.clone(),
225 output_types: self.output_types.clone(),
226 externs: self.externs.clone(),
227 traversals: self.traversals.clone(),
228 resolved_outputs: self.resolved_outputs.clone(),
229 _nodes: self._nodes.clone(),
230 drive: self.drive.clone(),
231 none: self.none.clone(),
232 ran: self.ran.clone(),
233 epoch: self.epoch,
234 all_ran: self.all_ran,
235 clean: self.clean.clone(),
236 use_clean: self.use_clean,
237 plan: self.plan.clone(),
238 volatile: self.volatile.clone(),
239 side: self.side.clone(),
240 slot_step: self.slot_step.clone(),
241 sites: self.sites.clone(),
242 cur_step: self.cur_step,
243 tracker: self.tracker,
244 all: self.all.clone(),
245 dirty: self.dirty.clone(),
246 any_none: self.any_none,
247 volatile_steps: self.volatile_steps.clone(),
248 };
249 core.republish_refs();
250 core
251 }
252}
253
254impl HybridCore {
255 fn republish_refs(&mut self) {
260 for &(slot, idx) in &self.ref_scratch {
261 let (p, l) = self.scratch[idx].ptr_len();
262 self.buffer[slot] = p;
263 self.buffer[slot + 1] = l;
264 }
265 self.externs.seed(&mut self.buffer, None);
266 }
267}
268
269impl HybridCore {
270 #[cfg(debug_assertions)]
275 fn validate_refs(&self) {
276 let skip = |slot: usize| {
279 self.none[slot]
280 || matches!(self.slot_step.get(slot), Some(Some(step)) if self.ran[*step] == 0)
281 };
282 for &(slot, idx) in &self.ref_scratch {
283 if skip(slot) {
284 continue;
285 }
286 let (p, l) = self.scratch[idx].ptr_len();
287 assert!(
288 self.buffer[slot] == p && self.buffer[slot + 1] == l,
289 "S9 ref-validator: slot pair ({slot}, {}) = ({:#x}, {}) \
290 does not match scratch[{idx}] = ({p:#x}, {l})",
291 slot + 1,
292 self.buffer[slot],
293 self.buffer[slot + 1],
294 );
295 }
296 }
297
298 #[inline]
300 fn guard_ref_slot(&self, slot: usize) {
301 if self.ref_slots.get(slot).copied().unwrap_or(false) {
302 panic!(
303 "S2 pointer containment: slot {slot} is Ref2-colored; raw u64 readers \
304 would leak an interior address. Use the typed borrow-checked accessor \
305 (read_vec_*), the boundary decode, or copy out."
306 );
307 }
308 }
309
310 fn ref_entry(&self, slot: usize) -> &crate::ast::ScratchBuf {
312 match self.ref_scratch.iter().find(|(s, _)| *s == slot) {
313 Some(&(_, idx)) => &self.scratch[idx],
314 None if self.ref_slots.get(slot).copied().unwrap_or(false) => panic!(
315 "slot {slot} is a Ref pair owned by the CALLER (a kernel \
316 input) — read it on the caller side"
317 ),
318 None => panic!("slot {slot} is not a Ref2-colored slot"),
319 }
320 }
321}
322
323impl HybridCore {
324 #[inline]
330 fn begin_epoch(&mut self) {
331 if self.externs.cells_dirty() {
332 self.externs.refresh_cells(&mut self.buffer);
333 }
334 self.dirty_refreshed();
335 self.epoch += 1;
336 self.all_ran = false;
337 for &i in self.volatile_steps.iter() {
338 self.clean[i] = false;
339 }
340 self.drive.stale = false;
341 }
342
343 #[cfg(feature = "jit")]
347 fn set_use_clean(&mut self, on: bool) {
348 self.use_clean = on;
349 let side = std::sync::Arc::clone(&self.side);
350 self.dirty = self
351 .plan
352 .input_dependents
353 .iter()
354 .map(|deps| {
355 if on {
356 deps.clone()
357 } else {
358 deps.iter().copied().filter(|&i| side[i]).collect()
359 }
360 })
361 .collect::<Vec<_>>()
362 .into();
363 }
364
365 #[inline]
370 fn dirty_refreshed(&mut self) {
371 if !self.externs.has_changed() {
372 return;
373 }
374 let changed = self.externs.take_changed();
375 for &slot in &changed {
376 if let Some(deps) = self.plan.input_dependents.get(slot) {
377 for &i in deps {
378 self.ran[i] = 0;
379 self.clean[i] = false;
380 }
381 self.all_ran = false;
382 }
383 }
384 self.externs.return_changed(changed);
385 }
386
387 #[inline]
391 fn refresh_cells(&mut self) {
392 if self.externs.cells_dirty() {
393 self.externs.refresh_cells(&mut self.buffer);
394 self.dirty_refreshed();
395 }
396 }
397
398 fn attach_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> Result<(), String> {
401 let slot = self.externs.attach_cell(name, cell)?;
402 self.dirty_input(slot);
403 self.drive.stale = true;
404 Ok(())
405 }
406
407 #[inline]
410 fn dirty_input(&mut self, slot: usize) {
411 if let Some(deps) = self.dirty.get(slot) {
412 for &i in deps {
413 self.clean[i] = false;
414 }
415 }
416 }
417
418 #[inline]
422 fn run_steps(&mut self, order: &[usize]) {
423 self.run_guarded(|core| core.run_order(order));
424 }
425
426 #[inline]
430 fn run_guarded(&mut self, body: impl FnOnce(&mut Self)) {
431 let capture = crate::kernel::engines::EvalPanicCaptureGuard::arm();
432 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| body(self)));
433 drop(capture);
434 if let Err(payload) = outcome {
435 let sites = std::sync::Arc::clone(&self.sites);
436 let node = self.steps[self.cur_step].failing_node(&self.buffer, self.tracker);
437 sites.reraise(payload, node, &self.buffer, Some(&self.none));
438 }
439 #[cfg(debug_assertions)]
440 self.validate_refs();
441 }
442
443 #[inline]
445 fn run_order(&mut self, order: &[usize]) {
446 let steps = &self.steps;
447 let none_free = !self.any_none;
448 for &i in order {
449 if self.all_ran || self.ran[i] == self.epoch {
450 continue;
451 }
452 let never = self.volatile[i];
453 if (self.use_clean || self.side[i]) && self.clean[i] && !never {
454 self.ran[i] = self.epoch;
455 continue;
456 }
457 self.cur_step = i;
458 run_hybrid_step(
459 &steps[i],
460 none_free,
461 &mut self.buffer,
462 &mut self.none,
463 &mut self.gather_buf,
464 &mut self.scatter_buf,
465 &mut self.scratch,
466 );
467 self.ran[i] = self.epoch;
468 self.clean[i] = !never;
469 }
470 }
471
472 #[inline]
478 fn run_fresh(&mut self) {
479 let steps = &self.steps;
480 for (i, step) in steps.iter().enumerate() {
481 if self.side[i] {
482 let never = self.volatile[i];
483 if self.clean[i] && !never {
484 continue;
485 }
486 self.clean[i] = !never;
487 }
488 self.cur_step = i;
489 run_hybrid_step(
490 step,
491 true,
492 &mut self.buffer,
493 &mut self.none,
494 &mut self.gather_buf,
495 &mut self.scatter_buf,
496 &mut self.scratch,
497 );
498 }
499 self.all_ran = true;
500 }
501
502 #[inline]
505 fn eval_all(&mut self) {
506 let fresh = self.drive.stale;
507 if fresh {
508 self.begin_epoch();
509 } else {
510 self.refresh_cells();
511 }
512 if fresh && !self.use_clean && !self.any_none {
513 self.run_guarded(|core| core.run_fresh());
514 } else {
515 let all = std::sync::Arc::clone(&self.all);
516 self.run_steps(&all);
517 }
518 }
519
520 fn pull_named(&mut self, name: &str) -> crate::ast::Value {
522 if self.drive.stale {
523 self.begin_epoch();
524 } else {
525 self.refresh_cells();
526 }
527 let plan = std::sync::Arc::clone(&self.plan);
528 if let Some(order) = plan.cones.get(name) {
529 self.run_steps(order);
530 }
531 self.value_of(name)
532 }
533
534 fn pull_at(&mut self, index: usize) -> crate::ast::Value {
538 if self.resolved_outputs.len() <= index {
539 self.resolved_outputs.resize(index + 1, None);
540 }
541 if self.resolved_outputs[index].is_none() {
542 let name = self
543 .externs
544 .output_names()
545 .get(index)
546 .cloned()
547 .unwrap_or_else(|| {
548 panic!(
549 "no output at index {index}; this kernel declares {}",
550 self.externs.output_names().len()
551 )
552 });
553 let slot = self.output_map[&name];
554 let ty = self
555 .output_types
556 .get(&name)
557 .copied()
558 .unwrap_or(crate::ast::PortType::U64);
559 let cone = self
560 .plan
561 .cones
562 .get(&name)
563 .map(|c| std::sync::Arc::from(c.as_slice()));
564 self.resolved_outputs[index] = Some((slot, ty, cone));
565 }
566 if self.drive.stale {
567 self.begin_epoch();
568 } else {
569 self.refresh_cells();
570 }
571 let (slot, ty, cone) = self.resolved_outputs[index]
572 .clone()
573 .expect("resolved above");
574 if let Some(order) = cone {
575 self.run_steps(&order);
576 }
577 self.slot_value(slot, ty)
578 }
579
580 fn value_of(&self, name: &str) -> crate::ast::Value {
583 let slot = self.output_map[name];
584 let ty = self
585 .output_types
586 .get(name)
587 .copied()
588 .unwrap_or(crate::ast::PortType::U64);
589 self.slot_value(slot, ty)
590 }
591
592 fn slot_value(&self, slot: usize, ty: crate::ast::PortType) -> crate::ast::Value {
595 if self.none.get(slot).copied().unwrap_or(false) {
596 return crate::ast::Value::None;
597 }
598 crate::compile::marshal::decode_output(&self.buffer, slot, ty)
599 }
600
601 fn plan(&self) -> crate::EnginePlan {
603 let (native_segments, closure_steps) = self.engine_counts();
604 crate::EnginePlan {
605 native_segments,
606 closure_steps,
607 interpreted_nodes: 0,
608 }
609 }
610
611 fn invalidate_all(&mut self) {
613 self.clean.fill(false);
614 self.all_ran = false;
615 self.drive.stale = true;
616 }
617}
618
619#[inline]
622fn eval_all_hybrid_steps(core: &mut HybridCore) {
623 core.drive.stale = true;
624 core.eval_all();
625}
626
627impl HybridCore {
632 fn engine_counts(&self) -> (usize, usize) {
635 let closures = self
636 .steps
637 .iter()
638 .filter(|s| matches!(s, HybridStep::Closure(_)))
639 .count();
640 (self.steps.len() - closures, closures)
641 }
642
643 fn set_extern(&mut self, name: &str, value: crate::ast::Value) -> Result<usize, String> {
648 let (slot, unset) = self.externs.set(name, value, &mut self.buffer)?;
649 self.extern_written(slot, unset);
650 Ok(slot)
651 }
652
653 fn set_extern_at(&mut self, index: usize, value: crate::ast::Value) -> Result<usize, String> {
655 let (slot, unset) = self.externs.set_at(index, value, &mut self.buffer)?;
656 self.extern_written(slot, unset);
657 Ok(slot)
658 }
659
660 fn extern_written(&mut self, slot: usize, unset: bool) {
666 self.none[slot] = unset;
667 let was = self.any_none;
668 self.any_none = self.externs.any_unset();
669 if was && !self.any_none {
670 self.none.fill(false);
671 }
672 self.dirty_input(slot);
673 self.drive.stale = true;
674 }
675}
676
677#[derive(Clone)]
682pub struct HybridKernelRaw {
683 core: HybridCore,
684}
685
686impl HybridKernelRaw {
687 #[inline]
690 fn set_coords(&mut self, coords: &[u64]) {
691 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
692 if self.core.buffer[i] != c {
693 self.core.buffer[i] = c;
694 self.core.dirty_input(i);
695 }
696 }
697 }
698
699 #[inline]
701 pub fn eval(&mut self, coords: &[u64]) {
702 self.set_coords(coords);
703 eval_all_hybrid_steps(&mut self.core);
704 }
705
706 #[cfg(feature = "jit")]
707 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
708 self.core.pull_named(name)
709 }
710
711 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
715 self.core.set_extern(name, value).map(|_| ())
716 }
717
718 pub fn set_input_at(&mut self, index: usize, value: crate::ast::Value) -> Result<(), String> {
720 self.core.set_extern_at(index, value).map(|_| ())
721 }
722
723 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
725 self.core.externs.names()
726 }
727
728 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
732 self.core.externs.cursor_schemas()
733 }
734
735 pub fn set_cursor(
739 &mut self,
740 name: &str,
741 partition: &crate::iteration::cursor_partition::Partition,
742 ) -> Result<(), String> {
743 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
744 self.set_input(&slot, value)?;
745 }
746 Ok(())
747 }
748
749 #[inline]
751 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
752 self.core.guard_ref_slot(slot);
753 self.eval(coords);
754 self.core.buffer[slot]
755 }
756
757 #[inline]
760 pub fn get(&self, name: &str) -> u64 {
761 let slot = self.core.output_map[name];
762 self.core.guard_ref_slot(slot);
763 self.core.buffer[slot]
764 }
765
766 #[inline]
769 pub fn get_slot(&self, slot: usize) -> u64 {
770 self.core.guard_ref_slot(slot);
771 self.core.buffer[slot]
772 }
773
774 crate::compile::ref_readers!();
775
776 pub fn get_value(&self, name: &str) -> crate::ast::Value {
780 self.core.value_of(name)
781 }
782
783 pub fn coord_count(&self) -> usize {
785 self.core.coord_count
786 }
787
788 pub fn engine_counts(&self) -> (usize, usize) {
791 self.core.engine_counts()
792 }
793
794 pub fn resolve_output(&self, name: &str) -> Option<usize> {
796 self.core.output_map.get(name).copied()
797 }
798
799 pub fn retain_nodes(&mut self, nodes: Vec<Box<dyn PolydatNode>>) {
801 self.core._nodes = std::sync::Arc::new(nodes);
802 }
803}
804
805#[derive(Clone)]
817pub struct HybridKernelPull {
818 core: HybridCore,
819 slot_provenance: Vec<crate::kernel::ProvMask>,
820 changed_mask: crate::kernel::ProvMask,
821 force_run: bool,
824}
825
826impl HybridKernelPull {
827 #[inline]
830 fn set_inputs(&mut self, coords: &[u64]) {
831 self.changed_mask.clear();
832 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
833 if self.core.buffer[i] != c {
834 self.core.buffer[i] = c;
835 self.changed_mask.set(i);
836 self.core.dirty_input(i);
837 }
838 }
839 }
840
841 #[inline]
843 pub fn eval(&mut self, coords: &[u64]) {
844 self.set_inputs(coords);
845 self.force_run = false;
846 eval_all_hybrid_steps(&mut self.core);
847 }
848
849 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
850 self.core.pull_named(name)
851 }
852
853 #[inline]
856 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
857 self.core.guard_ref_slot(slot);
858 self.set_inputs(coords);
859 if !self.force_run
860 && slot < self.slot_provenance.len()
861 && !self.slot_provenance[slot].intersects(&self.changed_mask)
862 {
863 return self.core.buffer[slot];
864 }
865 self.force_run = false;
866 eval_all_hybrid_steps(&mut self.core);
867 self.core.buffer[slot]
868 }
869
870 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
874 self.core.set_extern(name, value)?;
875 self.force_run = true;
876 Ok(())
877 }
878
879 pub fn set_input_at(&mut self, index: usize, value: crate::ast::Value) -> Result<(), String> {
881 self.core.set_extern_at(index, value)?;
882 self.force_run = true;
883 Ok(())
884 }
885
886 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
888 self.core.externs.names()
889 }
890
891 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
895 self.core.externs.cursor_schemas()
896 }
897
898 pub fn set_cursor(
902 &mut self,
903 name: &str,
904 partition: &crate::iteration::cursor_partition::Partition,
905 ) -> Result<(), String> {
906 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
907 self.set_input(&slot, value)?;
908 }
909 Ok(())
910 }
911
912 #[inline]
915 pub fn get(&self, name: &str) -> u64 {
916 let slot = self.core.output_map[name];
917 self.core.guard_ref_slot(slot);
918 self.core.buffer[slot]
919 }
920
921 #[inline]
924 pub fn get_slot(&self, slot: usize) -> u64 {
925 self.core.guard_ref_slot(slot);
926 self.core.buffer[slot]
927 }
928
929 crate::compile::ref_readers!();
930
931 pub fn get_value(&self, name: &str) -> crate::ast::Value {
935 self.core.value_of(name)
936 }
937
938 pub fn coord_count(&self) -> usize {
940 self.core.coord_count
941 }
942
943 pub fn engine_counts(&self) -> (usize, usize) {
946 self.core.engine_counts()
947 }
948
949 pub fn resolve_output(&self, name: &str) -> Option<usize> {
951 self.core.output_map.get(name).copied()
952 }
953
954 pub fn retain_nodes(&mut self, nodes: Vec<Box<dyn PolydatNode>>) {
956 self.core._nodes = std::sync::Arc::new(nodes);
957 }
958}
959
960#[derive(Clone)]
974pub struct HybridKernelPushPull {
975 core: HybridCore,
976 slot_provenance: Vec<crate::kernel::ProvMask>,
977 changed_mask: crate::kernel::ProvMask,
978 force_run: bool,
981}
982
983impl HybridKernelPushPull {
984 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
989 self.core.set_extern(name, value)?;
990 self.force_run = true;
991 Ok(())
992 }
993
994 pub fn set_input_at(&mut self, index: usize, value: crate::ast::Value) -> Result<(), String> {
996 self.core.set_extern_at(index, value)?;
997 self.force_run = true;
998 Ok(())
999 }
1000
1001 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
1003 self.core.externs.names()
1004 }
1005
1006 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
1010 self.core.externs.cursor_schemas()
1011 }
1012
1013 pub fn set_cursor(
1017 &mut self,
1018 name: &str,
1019 partition: &crate::iteration::cursor_partition::Partition,
1020 ) -> Result<(), String> {
1021 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
1022 self.set_input(&slot, value)?;
1023 }
1024 Ok(())
1025 }
1026
1027 #[inline]
1029 fn set_inputs(&mut self, coords: &[u64]) {
1030 self.changed_mask.clear();
1031 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
1032 if self.core.buffer[i] != c {
1033 self.core.buffer[i] = c;
1034 self.changed_mask.set(i);
1035 self.core.dirty_input(i);
1036 }
1037 }
1038 }
1039
1040 #[inline]
1042 pub fn eval(&mut self, coords: &[u64]) {
1043 self.set_inputs(coords);
1044 self.force_run = false;
1045 self.core.drive.stale = true;
1046 self.core.eval_all();
1047 }
1048
1049 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1050 self.core.pull_named(name)
1051 }
1052
1053 #[inline]
1055 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1056 self.core.guard_ref_slot(slot);
1057 self.set_inputs(coords);
1058 if !self.force_run
1059 && slot < self.slot_provenance.len()
1060 && !self.slot_provenance[slot].intersects(&self.changed_mask)
1061 {
1062 return self.core.buffer[slot];
1063 }
1064 self.force_run = false;
1065 self.core.drive.stale = true;
1066 self.core.eval_all();
1067 self.core.buffer[slot]
1068 }
1069
1070 #[inline]
1073 pub fn get(&self, name: &str) -> u64 {
1074 let slot = self.core.output_map[name];
1075 self.core.guard_ref_slot(slot);
1076 self.core.buffer[slot]
1077 }
1078
1079 #[inline]
1082 pub fn get_slot(&self, slot: usize) -> u64 {
1083 self.core.guard_ref_slot(slot);
1084 self.core.buffer[slot]
1085 }
1086
1087 crate::compile::ref_readers!();
1088
1089 pub fn get_value(&self, name: &str) -> crate::ast::Value {
1093 self.core.value_of(name)
1094 }
1095
1096 pub fn coord_count(&self) -> usize {
1098 self.core.coord_count
1099 }
1100
1101 pub fn engine_counts(&self) -> (usize, usize) {
1104 self.core.engine_counts()
1105 }
1106
1107 pub fn resolve_output(&self, name: &str) -> Option<usize> {
1109 self.core.output_map.get(name).copied()
1110 }
1111
1112 pub fn retain_nodes(&mut self, nodes: Vec<Box<dyn PolydatNode>>) {
1114 self.core._nodes = std::sync::Arc::new(nodes);
1115 }
1116}
1117
1118pub type HybridKernel = HybridKernelPushPull;
1123
1124fn flatten_input_slots(
1128 wiring: &[Vec<WireSource>],
1129 nodes: &[Box<dyn PolydatNode>],
1130 node_idx: usize,
1131 port_offsets: &[Vec<usize>],
1132 input_starts: &[usize],
1133 input_widths: &[usize],
1134) -> Vec<usize> {
1135 let mut slots = Vec::new();
1136 for source in &wiring[node_idx] {
1137 let (start, w) = match source {
1138 WireSource::Input(c) => (
1139 input_starts.get(*c).copied().unwrap_or(*c),
1140 input_widths.get(*c).copied().unwrap_or(1),
1141 ),
1142 WireSource::NodeOutput(u, p) => (
1143 port_offsets[*u][*p],
1144 nodes[*u].meta().outs[*p].typ.slot_width(),
1145 ),
1146 };
1147 slots.extend(start..start + w);
1148 }
1149 slots
1150}
1151
1152fn flatten_ref_output_starts(
1155 nodes: &[Box<dyn PolydatNode>],
1156 node_idx: usize,
1157 port_offsets: &[Vec<usize>],
1158) -> Vec<usize> {
1159 nodes[node_idx]
1160 .meta()
1161 .outs
1162 .iter()
1163 .enumerate()
1164 .filter(|(_, out)| out.typ.slot_color() == crate::ast::SlotColor::Ref2)
1165 .map(|(p, _)| port_offsets[node_idx][p])
1166 .collect()
1167}
1168
1169fn flatten_output_slots(
1171 nodes: &[Box<dyn PolydatNode>],
1172 node_idx: usize,
1173 port_offsets: &[Vec<usize>],
1174) -> Vec<usize> {
1175 let mut slots = Vec::new();
1176 for (p, out) in nodes[node_idx].meta().outs.iter().enumerate() {
1177 let start = port_offsets[node_idx][p];
1178 slots.extend(start..start + out.typ.slot_width());
1179 }
1180 slots
1181}
1182
1183#[cfg(feature = "jit")]
1191#[allow(clippy::too_many_arguments)]
1192pub(crate) fn build_hybrid(
1193 nodes: &[Box<dyn PolydatNode>],
1194 wiring: &[Vec<WireSource>],
1195 coord_count: usize,
1196 total_slots: usize,
1197 port_offsets: &[Vec<usize>],
1198 input_starts: &[usize],
1199 input_widths: &[usize],
1200 output_map: HashMap<String, usize>,
1201 ref_slots: Vec<bool>,
1202 input_types: &[crate::ast::PortType],
1203 externs: crate::compile::externs::Externs,
1204 constant: Vec<bool>,
1205 volatile: Vec<bool>,
1206 attribution: std::sync::Arc<crate::compile::Attribution>,
1207) -> Result<HybridKernelPushPull, String> {
1208 let mut steps: Vec<HybridStep> = Vec::new();
1209 let mut scratch: Vec<crate::ast::ScratchBuf> = Vec::new();
1210 let mut ref_scratch: Vec<(usize, usize)> = Vec::new();
1211 let mut max_inputs = 0usize;
1212 let mut max_outputs = 0usize;
1213
1214 let classifications: Vec<(JitOp, Vec<usize>, Vec<usize>)> = nodes
1216 .iter()
1217 .enumerate()
1218 .map(|(node_idx, node)| {
1219 let wire_types: Vec<crate::ast::PortType> = wiring[node_idx]
1223 .iter()
1224 .map(|src| match src {
1225 WireSource::Input(c) => input_types
1226 .get(*c)
1227 .copied()
1228 .unwrap_or(crate::ast::PortType::U64),
1229 WireSource::NodeOutput(j, p) => nodes[*j].meta().outs[*p].typ,
1230 })
1231 .collect();
1232 let jit_op = jit::classify_node_typed(node.as_ref(), &wire_types);
1233
1234 let input_slots = flatten_input_slots(
1235 wiring,
1236 nodes,
1237 node_idx,
1238 port_offsets,
1239 input_starts,
1240 input_widths,
1241 );
1242 let output_slots = flatten_output_slots(nodes, node_idx, port_offsets);
1243
1244 max_inputs = max_inputs.max(input_slots.len());
1245 max_outputs = max_outputs.max(output_slots.len());
1246
1247 (jit_op, input_slots, output_slots)
1248 })
1249 .collect();
1250 let mut classifications = classifications;
1255 let unset = externs.unset_slots();
1256 if !unset.is_empty() {
1257 let mut tainted = vec![false; nodes.len()];
1258 for node_idx in 0..nodes.len() {
1259 tainted[node_idx] = wiring[node_idx].iter().any(|src| match src {
1260 WireSource::Input(c) => unset.contains(&input_starts[*c]),
1261 WireSource::NodeOutput(j, _) => tainted[*j],
1262 });
1263 if tainted[node_idx] {
1264 classifications[node_idx].0 = JitOp::Fallback;
1265 }
1266 }
1267 }
1268
1269 let mut node_step = vec![usize::MAX; nodes.len()];
1271 let order: Vec<usize> = (0..nodes.len())
1279 .filter(|&k| constant[k])
1280 .chain((0..nodes.len()).filter(|&k| !constant[k]))
1281 .collect();
1282 let mut pos = 0;
1284 while pos < order.len() {
1285 let i = order[pos];
1286 if matches!(classifications[i].0, JitOp::Fallback) {
1287 let node = &nodes[i];
1291 let (_, ref input_slots, ref output_slots) = classifications[i];
1292 let scratch_start = scratch.len();
1293 let wire_types: Vec<crate::ast::PortType> = wiring[i]
1294 .iter()
1295 .map(|src| match src {
1296 WireSource::Input(c) => input_types
1297 .get(*c)
1298 .copied()
1299 .unwrap_or(crate::ast::PortType::U64),
1300 WireSource::NodeOutput(j, p) => nodes[*j].meta().outs[*p].typ,
1301 })
1302 .collect();
1303 let op = if let Some(op) = node.compiled_u64() {
1304 ClosureOp::U64(op)
1305 } else if let Some(op) = crate::compile::assembly::identity_op(node.as_ref()) {
1306 ClosureOp::U64(op)
1307 } else if let Some(kit) = ref_copy_or_slot(node.as_ref(), &wire_types) {
1308 scratch.extend(kit.scratch.iter().map(|e| crate::ast::ScratchBuf::new(*e)));
1309 let starts = flatten_ref_output_starts(nodes, i, port_offsets);
1310 ref_scratch.extend(crate::compile::assembly::scratch_pairs(
1311 &node.meta().name,
1312 &starts,
1313 &kit.scratch,
1314 scratch_start,
1315 ));
1316 ClosureOp::Slot(kit.op)
1317 } else {
1318 return Err(format!(
1319 "node '{}' has no compiled form and can't be JIT-compiled",
1320 node.meta().name
1321 ));
1322 };
1323 node_step[i] = steps.len();
1324 steps.push(HybridStep::Closure(ClosureStep {
1325 op,
1326 input_slots: input_slots.clone(),
1327 output_slots: output_slots.clone(),
1328 scratch_range: (scratch_start, scratch.len()),
1329 accepts_none: node.accepts_none_inputs(),
1330 node: i,
1331 }));
1332 pos += 1;
1333 } else {
1334 let is_side =
1344 |k: usize| matches!(nodes[k].purity(), crate::ast::Purity::SideChannel { .. });
1345 let batch_start = pos;
1346 let first = order[batch_start];
1347 while pos < order.len()
1348 && !matches!(classifications[order[pos]].0, JitOp::Fallback)
1349 && constant[order[pos]] == constant[first]
1350 && volatile[order[pos]] == volatile[first]
1351 && !is_side(order[pos])
1352 && !is_side(first)
1353 {
1354 pos += 1;
1355 }
1356 if pos == batch_start {
1357 pos += 1;
1358 }
1359 let members: Vec<usize> = order[batch_start..pos].to_vec();
1360 for &k in &members {
1364 let base = scratch.len();
1365 classifications[k].0.place_scratch(base);
1366 let elems = classifications[k].0.scratch_elems().to_vec();
1367 ref_scratch.extend(crate::compile::assembly::scratch_pairs(
1368 &nodes[k].meta().name,
1369 &flatten_ref_output_starts(nodes, k, port_offsets),
1370 &elems,
1371 base,
1372 ));
1373 scratch.extend(elems.iter().map(|e| crate::ast::ScratchBuf::new(*e)));
1374 }
1375 let batch: Vec<(JitOp, Vec<usize>, Vec<usize>)> = members
1383 .iter()
1384 .map(|&k| classifications[k].clone())
1385 .collect();
1386 let written: std::collections::HashSet<usize> = batch
1387 .iter()
1388 .flat_map(|(_, _, o)| o.iter().copied())
1389 .collect();
1390 let mut input_slots: Vec<usize> = Vec::new();
1391 for (_, ins, _) in &batch {
1392 for &s in ins {
1393 if !written.contains(&s) && !input_slots.contains(&s) {
1394 input_slots.push(s);
1395 }
1396 }
1397 }
1398 let output_slots: Vec<usize> = batch
1399 .iter()
1400 .flat_map(|(_, _, o)| o.iter().copied())
1401 .collect();
1402 let (code_fn, code) = jit::compile_jit_entry(&batch, Some(total_slots))?;
1403 let segment = steps.len();
1404 for &k in &members {
1405 node_step[k] = segment;
1406 }
1407 steps.push(HybridStep::Jit(JitSegment {
1408 code_fn,
1409 fallible: code.fallible(),
1410 _module: code,
1411 input_slots,
1412 output_slots,
1413 nodes: members,
1414 }));
1415 }
1416 }
1417
1418 let output_types = output_types_of(nodes, port_offsets, input_starts, input_types, &output_map);
1419 build_pushpull_from_steps(
1420 steps,
1421 scratch,
1422 ref_scratch,
1423 ref_slots,
1424 wiring,
1425 nodes,
1426 coord_count,
1427 total_slots,
1428 output_map,
1429 max_inputs,
1430 max_outputs,
1431 input_starts,
1432 input_widths,
1433 output_types,
1434 externs,
1435 constant,
1436 volatile,
1437 attribution,
1438 node_step,
1439 )
1440}
1441
1442fn output_types_of(
1445 nodes: &[Box<dyn PolydatNode>],
1446 port_offsets: &[Vec<usize>],
1447 input_starts: &[usize],
1448 input_types: &[crate::ast::PortType],
1449 output_map: &HashMap<String, usize>,
1450) -> HashMap<String, crate::ast::PortType> {
1451 let mut slot_types: HashMap<usize, crate::ast::PortType> = HashMap::new();
1452 for (start, ty) in input_starts.iter().zip(input_types) {
1453 slot_types.insert(*start, *ty);
1454 }
1455 for (node_idx, node) in nodes.iter().enumerate() {
1456 for (p, out) in node.meta().outs.iter().enumerate() {
1457 slot_types.insert(port_offsets[node_idx][p], out.typ);
1458 }
1459 }
1460 output_map
1461 .iter()
1462 .map(|(name, slot)| {
1463 (
1464 name.clone(),
1465 slot_types
1466 .get(slot)
1467 .copied()
1468 .unwrap_or(crate::ast::PortType::U64),
1469 )
1470 })
1471 .collect()
1472}
1473
1474#[cfg(not(feature = "jit"))]
1476#[allow(clippy::too_many_arguments)]
1477pub(crate) fn build_hybrid(
1478 nodes: &[Box<dyn PolydatNode>],
1479 wiring: &[Vec<WireSource>],
1480 coord_count: usize,
1481 total_slots: usize,
1482 port_offsets: &[Vec<usize>],
1483 input_starts: &[usize],
1484 input_widths: &[usize],
1485 output_map: HashMap<String, usize>,
1486 ref_slots: Vec<bool>,
1487 input_types: &[crate::ast::PortType],
1488 externs: crate::compile::externs::Externs,
1489 constant: Vec<bool>,
1490 volatile: Vec<bool>,
1491 attribution: std::sync::Arc<crate::compile::Attribution>,
1492) -> Result<HybridKernelPushPull, String> {
1493 let mut steps: Vec<HybridStep> = Vec::new();
1494 let mut scratch: Vec<crate::ast::ScratchBuf> = Vec::new();
1495 let mut ref_scratch: Vec<(usize, usize)> = Vec::new();
1496 let mut max_inputs = 0usize;
1497 let mut max_outputs = 0usize;
1498
1499 for (node_idx, node) in nodes.iter().enumerate() {
1500 let input_slots = flatten_input_slots(
1501 wiring,
1502 nodes,
1503 node_idx,
1504 port_offsets,
1505 input_starts,
1506 input_widths,
1507 );
1508 let output_slots = flatten_output_slots(nodes, node_idx, port_offsets);
1509
1510 max_inputs = max_inputs.max(input_slots.len());
1511 max_outputs = max_outputs.max(output_slots.len());
1512
1513 let scratch_start = scratch.len();
1514 let wire_types: Vec<crate::ast::PortType> = wiring[node_idx]
1515 .iter()
1516 .map(|src| match src {
1517 WireSource::Input(c) => input_types
1518 .get(*c)
1519 .copied()
1520 .unwrap_or(crate::ast::PortType::U64),
1521 WireSource::NodeOutput(j, p) => nodes[*j].meta().outs[*p].typ,
1522 })
1523 .collect();
1524 let op = if let Some(op) = node.compiled_u64() {
1525 ClosureOp::U64(op)
1526 } else if let Some(op) = crate::compile::assembly::identity_op(node.as_ref()) {
1527 ClosureOp::U64(op)
1528 } else if let Some(kit) = ref_copy_or_slot(node.as_ref(), &wire_types) {
1529 scratch.extend(kit.scratch.iter().map(|e| crate::ast::ScratchBuf::new(*e)));
1530 let starts = flatten_ref_output_starts(nodes, node_idx, port_offsets);
1531 ref_scratch.extend(crate::compile::assembly::scratch_pairs(
1532 &node.meta().name,
1533 &starts,
1534 &kit.scratch,
1535 scratch_start,
1536 ));
1537 ClosureOp::Slot(kit.op)
1538 } else {
1539 return Err(format!("node '{}' has no compiled form", node.meta().name));
1540 };
1541 steps.push(HybridStep::Closure(ClosureStep {
1542 op,
1543 input_slots,
1544 output_slots,
1545 scratch_range: (scratch_start, scratch.len()),
1546 accepts_none: node.accepts_none_inputs(),
1547 node: node_idx,
1548 }));
1549 }
1550 let node_step: Vec<usize> = (0..nodes.len()).collect();
1551
1552 let output_types = output_types_of(nodes, port_offsets, input_starts, input_types, &output_map);
1553 build_pushpull_from_steps(
1554 steps,
1555 scratch,
1556 ref_scratch,
1557 ref_slots,
1558 wiring,
1559 nodes,
1560 coord_count,
1561 total_slots,
1562 output_map,
1563 max_inputs,
1564 max_outputs,
1565 input_starts,
1566 input_widths,
1567 output_types,
1568 externs,
1569 constant,
1570 volatile,
1571 attribution,
1572 node_step,
1573 )
1574}
1575
1576#[allow(clippy::too_many_arguments)]
1582fn build_pushpull_from_steps(
1583 steps: Vec<HybridStep>,
1584 scratch: Vec<crate::ast::ScratchBuf>,
1585 ref_scratch: Vec<(usize, usize)>,
1586 ref_slots: Vec<bool>,
1587 wiring: &[Vec<WireSource>],
1588 nodes: &[Box<dyn PolydatNode>],
1589 coord_count: usize,
1590 total_slots: usize,
1591 output_map: HashMap<String, usize>,
1592 max_inputs: usize,
1593 max_outputs: usize,
1594 _input_starts: &[usize],
1595 input_widths: &[usize],
1596 output_types: HashMap<String, crate::ast::PortType>,
1597 externs: crate::compile::externs::Externs,
1598 constant: Vec<bool>,
1599 volatile: Vec<bool>,
1600 attribution: std::sync::Arc<crate::compile::Attribution>,
1601 node_step: Vec<usize>,
1602) -> Result<HybridKernelPushPull, String> {
1603 let step_count = steps.len();
1604 debug_assert_eq!(node_step.len(), nodes.len());
1605 debug_assert!(node_step.iter().all(|&s| s < step_count));
1606 let to_steps = |list: &[usize]| -> Vec<usize> {
1609 let mut v: Vec<usize> = list.iter().map(|&n| node_step[n]).collect();
1610 v.sort_unstable();
1611 v.dedup();
1612 v
1613 };
1614 let mut buffer = vec![0u64; total_slots + 1];
1616 let mut none = vec![false; total_slots];
1617 let any_none = externs.seed(&mut buffer, Some(&mut none));
1618
1619 let node_provenance = crate::kernel::PolydatProgram::compute_provenance(nodes, wiring);
1626 let input_dependents: Vec<Vec<usize>> =
1627 crate::kernel::PolydatProgram::compute_dependents(&node_provenance, input_widths.len())
1628 .iter()
1629 .map(|d| to_steps(d))
1630 .collect();
1631 let step_dependents: Vec<Vec<usize>> = input_widths
1632 .iter()
1633 .enumerate()
1634 .flat_map(|(i, w)| {
1635 std::iter::repeat_n(input_dependents.get(i).cloned().unwrap_or_default(), *w)
1636 })
1637 .collect();
1638
1639 let step_outs: Vec<&[usize]> = steps.iter().map(|s| s.output_slots()).collect();
1640 let slot_provenance =
1641 crate::compile::slot_provenance(coord_count, total_slots, &step_outs, &step_dependents);
1642
1643 debug_assert_eq!(constant.len(), nodes.len());
1648 debug_assert_eq!(volatile.len(), nodes.len());
1649 let mut step_constant = vec![true; step_count];
1650 let mut step_volatile = vec![false; step_count];
1651 let mut side = vec![false; step_count];
1652 for (n, node) in nodes.iter().enumerate() {
1653 let s = node_step[n];
1654 step_constant[s] &= constant[n];
1655 step_volatile[s] |= volatile[n];
1656 side[s] |= matches!(node.purity(), crate::ast::Purity::SideChannel { .. });
1657 }
1658 let volatile = step_volatile;
1659 let constants: Vec<usize> = (0..step_count).filter(|&i| step_constant[i]).collect();
1660 let step_inputs: Vec<&[usize]> = steps.iter().map(|s| s.input_slots()).collect();
1661 let step_outputs: Vec<&[usize]> = steps.iter().map(|s| s.output_slots()).collect();
1662 let plan = crate::compile::Invalidation::from_provenance(
1663 step_dependents.clone(),
1664 &step_inputs,
1665 &step_outputs,
1666 &output_map,
1667 total_slots,
1668 );
1669 let mut slot_step: Vec<Option<usize>> = vec![None; total_slots];
1670 for (i, outs) in step_outputs.iter().enumerate() {
1671 for &s in outs.iter() {
1672 slot_step[s] = Some(i);
1673 }
1674 }
1675 drop(step_inputs);
1676 drop(step_outputs);
1677
1678 let dirty: Vec<Vec<usize>> = plan.input_dependents.clone();
1679 let volatile_steps: Vec<usize> = (0..step_count).filter(|&i| volatile[i]).collect();
1680 let mut kernel = HybridKernelPushPull {
1681 core: HybridCore {
1682 buffer,
1683 coord_count,
1684 steps: std::sync::Arc::new(steps),
1685 output_map,
1686 gather_buf: vec![0u64; max_inputs.max(1)],
1687 scatter_buf: vec![0u64; max_outputs.max(1)],
1688 scratch,
1689 ref_slots,
1690 ref_scratch,
1691 output_types,
1692 externs,
1693 traversals: Vec::new().into(),
1694 resolved_outputs: Vec::new(),
1695 _nodes: std::sync::Arc::new(Vec::new()),
1696 drive: crate::compile::Drive {
1697 coords: Vec::new(),
1698 stale: true,
1699 },
1700 none,
1701 ran: vec![0; step_count],
1702 epoch: 0,
1703 all_ran: false,
1704 clean: vec![false; step_count],
1705 use_clean: true,
1706 plan: std::sync::Arc::new(plan),
1707 volatile: volatile.into(),
1708 side: side.into(),
1709 slot_step: slot_step.into(),
1710 sites: attribution,
1711 cur_step: 0,
1712 tracker: total_slots,
1713 all: (0..step_count).collect::<Vec<usize>>().into(),
1714 dirty: dirty.into(),
1715 any_none,
1716 volatile_steps: volatile_steps.into(),
1717 },
1718 slot_provenance,
1719 changed_mask: crate::kernel::ProvMask::all_below(coord_count), force_run: false,
1721 };
1722 kernel.core.begin_epoch();
1727 kernel.core.run_steps(&constants);
1728 kernel.core.drive.stale = true;
1729 Ok(kernel)
1730}
1731
1732fn ref_copy_or_slot(
1736 node: &dyn PolydatNode,
1737 wire_types: &[crate::ast::PortType],
1738) -> Option<crate::ast::CompiledSlotKit> {
1739 let meta = node.meta();
1740 if (meta.name == "identity" || meta.name.starts_with("__port_"))
1741 && meta.outs.len() == 1
1742 && meta.outs[0].typ.slot_color() == crate::ast::SlotColor::Ref2
1743 {
1744 return crate::compile::assembly::ref_copy_kit(meta.outs[0].typ);
1745 }
1746 node.compiled_slot(wire_types)
1747}
1748
1749#[cfg(feature = "jit")]
1752impl HybridKernelRaw {
1753 fn mark_all_dirty(&mut self) {}
1755}
1756
1757impl HybridKernelPull {
1758 fn mark_all_dirty(&mut self) {
1760 self.changed_mask = crate::kernel::ProvMask::all_below(self.core.coord_count);
1761 self.force_run = true;
1762 }
1763}
1764
1765impl HybridKernelPushPull {
1766 fn mark_all_dirty(&mut self) {
1768 self.core.clean.fill(false);
1769 self.changed_mask = crate::kernel::ProvMask::all_below(self.core.coord_count);
1770 self.force_run = true;
1771 }
1772
1773 #[cfg(feature = "jit")]
1776 pub(crate) fn into_raw(self) -> HybridKernelRaw {
1777 let mut core = self.core;
1778 core.set_use_clean(false);
1779 HybridKernelRaw { core }
1780 }
1781
1782 #[cfg(feature = "jit")]
1784 pub(crate) fn into_pull(self) -> HybridKernelPull {
1785 let mut core = self.core;
1786 core.set_use_clean(false);
1787 let changed_mask = crate::kernel::ProvMask::all_below(core.coord_count);
1788 HybridKernelPull {
1789 core,
1790 slot_provenance: self.slot_provenance,
1791 changed_mask,
1792 force_run: false,
1793 }
1794 }
1795}
1796
1797use crate::compile::select::{Engine, Provenance};
1798
1799#[cfg(feature = "jit")]
1800crate::compile::impl_kernel_trait!(HybridKernelRaw, Engine::Native(Provenance::Raw));
1801crate::compile::impl_kernel_trait!(HybridKernelPull, Engine::Native(Provenance::Pull));
1802crate::compile::impl_kernel_trait!(HybridKernelPushPull, Engine::Native(Provenance::PushPull));
1803
1804macro_rules! hybrid_drive {
1808 ($ty:ident, $set_coords:ident) => {
1809 impl $ty {
1810 fn pull_value(&mut self, name: &str) -> crate::ast::Value {
1814 let coords = std::mem::take(&mut self.core.drive.coords);
1815 self.$set_coords(&coords);
1816 self.core.drive.coords = coords;
1817 self.pull_output(name)
1818 }
1819 fn pull_value_at(&mut self, index: usize) -> crate::ast::Value {
1821 let coords = std::mem::take(&mut self.core.drive.coords);
1822 self.$set_coords(&coords);
1823 self.core.drive.coords = coords;
1824 self.core.pull_at(index)
1825 }
1826 fn eval_pending(&mut self) {
1827 let coords = std::mem::take(&mut self.core.drive.coords);
1828 self.eval(&coords);
1829 self.core.drive.coords = coords;
1830 }
1831 }
1832 };
1833}
1834#[cfg(feature = "jit")]
1835hybrid_drive!(HybridKernelRaw, set_coords);
1836hybrid_drive!(HybridKernelPull, set_inputs);
1837hybrid_drive!(HybridKernelPushPull, set_inputs);
1838
1839#[inline(always)]
1846fn run_hybrid_step(
1847 step: &HybridStep,
1848 none_free: bool,
1849 buffer: &mut [u64],
1850 none: &mut [bool],
1851 gather: &mut [u64],
1852 scatter: &mut [u64],
1853 scratch: &mut [crate::ast::ScratchBuf],
1854) {
1855 if !none_free && step.input_slots().iter().any(|&s| none[s]) {
1856 #[cfg(feature = "jit")]
1857 if let HybridStep::Jit(_) = step {
1858 panic!(
1859 "a `None` reached native code in a hybrid kernel: an extern was cleared \
1860 after the build (docs/design/engine_parity.md, A12)"
1861 );
1862 }
1863 if !step.accepts_none() {
1864 for &s in step.output_slots() {
1865 none[s] = true;
1866 }
1867 return;
1868 }
1869 }
1870 match step {
1871 #[cfg(feature = "jit")]
1872 HybridStep::Jit(seg) => {
1873 let code_fn = seg.code_fn;
1877 let buf_const = buffer.as_ptr();
1878 let buf_mut = buffer.as_mut_ptr();
1879 let sc = scratch.as_mut_ptr();
1880 if seg.fallible {
1881 crate::compile::jit::invoke_with_catch(move || unsafe {
1882 (code_fn)(buf_const, buf_mut, sc);
1883 });
1884 } else {
1885 unsafe { (code_fn)(buf_const, buf_mut, sc) };
1886 }
1887 }
1888 HybridStep::Closure(cs) => {
1889 for (i, &slot) in cs.input_slots.iter().enumerate() {
1890 gather[i] = buffer[slot];
1891 }
1892 match &cs.op {
1893 ClosureOp::U64(op) => op(
1894 &gather[..cs.input_slots.len()],
1895 &mut scatter[..cs.output_slots.len()],
1896 ),
1897 ClosureOp::Slot(op) => op(
1898 &gather[..cs.input_slots.len()],
1899 &mut scatter[..cs.output_slots.len()],
1900 &mut scratch[cs.scratch_range.0..cs.scratch_range.1],
1901 ),
1902 }
1903 for (i, &slot) in cs.output_slots.iter().enumerate() {
1904 buffer[slot] = scatter[i];
1905 }
1906 }
1907 }
1908 if !none_free {
1909 for &s in step.output_slots() {
1910 none[s] = false;
1911 }
1912 }
1913}