1use std::collections::HashMap;
23
24use crate::ast::SlotShape;
25use crate::ast::{CompiledU64Op, PolydatNode};
26use crate::kernel::WireSource;
27
28#[cfg(feature = "jit")]
29use crate::compile::jit::{self, JitOp};
30
31enum HybridStep {
33 #[cfg(feature = "jit")]
36 Jit(JitSegment),
37 Closure(ClosureStep),
39}
40
41#[cfg(feature = "jit")]
42struct JitSegment {
43 code_fn: crate::compile::jit::NativeFn,
44 _module: crate::compile::jit::JitCode,
47 fallible: bool,
50 input_slots: Vec<usize>,
53 output_slots: Vec<usize>,
54 nodes: Vec<usize>,
57}
58
59impl HybridStep {
60 fn input_slots(&self) -> &[usize] {
61 match self {
62 #[cfg(feature = "jit")]
63 HybridStep::Jit(seg) => &seg.input_slots,
64 HybridStep::Closure(cs) => &cs.input_slots,
65 }
66 }
67 fn output_slots(&self) -> &[usize] {
68 match self {
69 #[cfg(feature = "jit")]
70 HybridStep::Jit(seg) => &seg.output_slots,
71 HybridStep::Closure(cs) => &cs.output_slots,
72 }
73 }
74 fn accepts_none(&self) -> bool {
77 match self {
78 #[cfg(feature = "jit")]
79 HybridStep::Jit(_) => false,
80 HybridStep::Closure(cs) => cs.accepts_none,
81 }
82 }
83
84 #[cfg_attr(not(feature = "jit"), allow(unused_variables))]
87 fn failing_node(&self, buffer: &[u64], tracker: usize) -> usize {
88 match self {
89 #[cfg(feature = "jit")]
90 HybridStep::Jit(seg) => seg
91 .nodes
92 .get(buffer[tracker] as usize)
93 .copied()
94 .unwrap_or(usize::MAX),
95 HybridStep::Closure(cs) => cs.node,
96 }
97 }
98}
99
100enum ClosureOp {
103 U64(CompiledU64Op),
104 Slot(crate::ast::CompiledSlotOp),
105}
106
107struct ClosureStep {
108 op: ClosureOp,
109 input_slots: Vec<usize>,
110 output_slots: Vec<usize>,
111 scratch_range: (usize, usize),
113 accepts_none: bool,
115 node: usize,
117}
118
119type ResolvedOutput = (usize, crate::ast::PortType, Option<std::sync::Arc<[usize]>>);
122
123struct HybridCore {
129 buffer: Vec<u64>,
130 coord_count: usize,
131 steps: std::sync::Arc<Vec<HybridStep>>,
132 output_map: HashMap<String, usize>,
133 gather_buf: Vec<u64>,
134 scatter_buf: Vec<u64>,
135 scratch: Vec<crate::ast::ScratchBuf>,
138 ref_slots: Vec<bool>,
140 ref_scratch: Vec<(usize, usize)>,
142 output_types: HashMap<String, crate::ast::PortType>,
144 externs: crate::compile::externs::Externs,
146 traversals: std::sync::Arc<[crate::dsl::traversal::Traversal]>,
149 resolved_outputs: Vec<Option<ResolvedOutput>>,
152 _nodes: std::sync::Arc<Vec<Box<dyn PolydatNode>>>,
154 drive: crate::compile::Drive,
158 none: Vec<bool>,
160 ran: Vec<u64>,
163 epoch: u64,
169 all_ran: bool,
171 clean: Vec<bool>,
175 use_clean: bool,
177 plan: std::sync::Arc<crate::compile::Invalidation>,
180 volatile: std::sync::Arc<[bool]>,
182 side: std::sync::Arc<[bool]>,
184 slot_step: std::sync::Arc<[Option<usize>]>,
186 sites: std::sync::Arc<crate::compile::Attribution>,
188 cur_step: usize,
190 tracker: usize,
193 all: std::sync::Arc<[usize]>,
195 dirty: std::sync::Arc<[Vec<usize>]>,
201 any_none: bool,
205 volatile_steps: std::sync::Arc<[usize]>,
207}
208
209impl Clone for HybridCore {
210 fn clone(&self) -> Self {
211 let mut core = HybridCore {
212 buffer: self.buffer.clone(),
213 coord_count: self.coord_count,
214 steps: self.steps.clone(),
215 output_map: self.output_map.clone(),
216 gather_buf: self.gather_buf.clone(),
217 scatter_buf: self.scatter_buf.clone(),
218 scratch: self.scratch.clone(),
219 ref_slots: self.ref_slots.clone(),
220 ref_scratch: self.ref_scratch.clone(),
221 output_types: self.output_types.clone(),
222 externs: self.externs.clone(),
223 traversals: self.traversals.clone(),
224 resolved_outputs: self.resolved_outputs.clone(),
225 _nodes: self._nodes.clone(),
226 drive: self.drive.clone(),
227 none: self.none.clone(),
228 ran: self.ran.clone(),
229 epoch: self.epoch,
230 all_ran: self.all_ran,
231 clean: self.clean.clone(),
232 use_clean: self.use_clean,
233 plan: self.plan.clone(),
234 volatile: self.volatile.clone(),
235 side: self.side.clone(),
236 slot_step: self.slot_step.clone(),
237 sites: self.sites.clone(),
238 cur_step: self.cur_step,
239 tracker: self.tracker,
240 all: self.all.clone(),
241 dirty: self.dirty.clone(),
242 any_none: self.any_none,
243 volatile_steps: self.volatile_steps.clone(),
244 };
245 core.republish_refs();
246 core
247 }
248}
249
250impl HybridCore {
251 fn republish_refs(&mut self) {
256 for &(slot, idx) in &self.ref_scratch {
257 let (p, l) = self.scratch[idx].ptr_len();
258 self.buffer[slot] = p;
259 self.buffer[slot + 1] = l;
260 }
261 self.externs.seed(&mut self.buffer, None);
262 }
263}
264
265impl HybridCore {
266 #[cfg(debug_assertions)]
271 fn validate_refs(&self) {
272 let skip = |slot: usize| {
275 self.none[slot]
276 || matches!(self.slot_step.get(slot), Some(Some(step)) if self.ran[*step] == 0)
277 };
278 for &(slot, idx) in &self.ref_scratch {
279 if skip(slot) {
280 continue;
281 }
282 let (p, l) = self.scratch[idx].ptr_len();
283 assert!(
284 self.buffer[slot] == p && self.buffer[slot + 1] == l,
285 "S9 ref-validator: slot pair ({slot}, {}) = ({:#x}, {}) \
286 does not match scratch[{idx}] = ({p:#x}, {l})",
287 slot + 1,
288 self.buffer[slot],
289 self.buffer[slot + 1],
290 );
291 }
292 }
293
294 #[inline]
296 fn guard_ref_slot(&self, slot: usize) {
297 if self.ref_slots.get(slot).copied().unwrap_or(false) {
298 panic!(
299 "S2 pointer containment: slot {slot} is Ref2-colored; raw u64 readers \
300 would leak an interior address. Use the typed borrow-checked accessor \
301 (read_vec_*), the boundary decode, or copy out."
302 );
303 }
304 }
305
306 fn ref_entry(&self, slot: usize) -> &crate::ast::ScratchBuf {
308 match self.ref_scratch.iter().find(|(s, _)| *s == slot) {
309 Some(&(_, idx)) => &self.scratch[idx],
310 None if self.ref_slots.get(slot).copied().unwrap_or(false) => panic!(
311 "slot {slot} is a Ref pair owned by the CALLER (a kernel \
312 input) — read it on the caller side"
313 ),
314 None => panic!("slot {slot} is not a Ref2-colored slot"),
315 }
316 }
317}
318
319impl HybridCore {
320 #[inline]
326 fn begin_epoch(&mut self) {
327 if self.externs.cells_dirty() {
328 self.externs.refresh_cells(&mut self.buffer);
329 }
330 self.dirty_refreshed();
331 self.epoch += 1;
332 self.all_ran = false;
333 for &i in self.volatile_steps.iter() {
334 self.clean[i] = false;
335 }
336 self.drive.stale = false;
337 }
338
339 #[cfg(feature = "jit")]
343 fn set_use_clean(&mut self, on: bool) {
344 self.use_clean = on;
345 let side = std::sync::Arc::clone(&self.side);
346 self.dirty = self
347 .plan
348 .input_dependents
349 .iter()
350 .map(|deps| {
351 if on {
352 deps.clone()
353 } else {
354 deps.iter().copied().filter(|&i| side[i]).collect()
355 }
356 })
357 .collect::<Vec<_>>()
358 .into();
359 }
360
361 #[inline]
366 fn dirty_refreshed(&mut self) {
367 if !self.externs.has_changed() {
368 return;
369 }
370 let changed = self.externs.take_changed();
371 for &slot in &changed {
372 if let Some(deps) = self.plan.input_dependents.get(slot) {
373 for &i in deps {
374 self.ran[i] = 0;
375 self.clean[i] = false;
376 }
377 self.all_ran = false;
378 }
379 }
380 self.externs.return_changed(changed);
381 }
382
383 #[inline]
387 fn refresh_cells(&mut self) {
388 if self.externs.cells_dirty() {
389 self.externs.refresh_cells(&mut self.buffer);
390 self.dirty_refreshed();
391 }
392 }
393
394 fn attach_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> Result<(), String> {
397 let slot = self.externs.attach_cell(name, cell)?;
398 self.dirty_input(slot);
399 self.drive.stale = true;
400 Ok(())
401 }
402
403 #[inline]
406 fn dirty_input(&mut self, slot: usize) {
407 if let Some(deps) = self.dirty.get(slot) {
408 for &i in deps {
409 self.clean[i] = false;
410 }
411 }
412 }
413
414 #[inline]
418 fn run_steps(&mut self, order: &[usize]) {
419 self.run_guarded(|core| core.run_order(order));
420 }
421
422 #[inline]
426 fn run_guarded(&mut self, body: impl FnOnce(&mut Self)) {
427 let capture = crate::kernel::engines::EvalPanicCaptureGuard::arm();
428 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| body(self)));
429 drop(capture);
430 if let Err(payload) = outcome {
431 let sites = std::sync::Arc::clone(&self.sites);
432 let node = self.steps[self.cur_step].failing_node(&self.buffer, self.tracker);
433 sites.reraise(payload, node, &self.buffer, Some(&self.none));
434 }
435 #[cfg(debug_assertions)]
436 self.validate_refs();
437 }
438
439 #[inline]
441 fn run_order(&mut self, order: &[usize]) {
442 let steps = &self.steps;
443 let none_free = !self.any_none;
444 for &i in order {
445 if self.all_ran || self.ran[i] == self.epoch {
446 continue;
447 }
448 let never = self.volatile[i];
449 if (self.use_clean || self.side[i]) && self.clean[i] && !never {
450 self.ran[i] = self.epoch;
451 continue;
452 }
453 self.cur_step = i;
454 run_hybrid_step(
455 &steps[i],
456 none_free,
457 &mut self.buffer,
458 &mut self.none,
459 &mut self.gather_buf,
460 &mut self.scatter_buf,
461 &mut self.scratch,
462 );
463 self.ran[i] = self.epoch;
464 self.clean[i] = !never;
465 }
466 }
467
468 #[inline]
474 fn run_fresh(&mut self) {
475 let steps = &self.steps;
476 for (i, step) in steps.iter().enumerate() {
477 if self.side[i] {
478 let never = self.volatile[i];
479 if self.clean[i] && !never {
480 continue;
481 }
482 self.clean[i] = !never;
483 }
484 self.cur_step = i;
485 run_hybrid_step(
486 step,
487 true,
488 &mut self.buffer,
489 &mut self.none,
490 &mut self.gather_buf,
491 &mut self.scatter_buf,
492 &mut self.scratch,
493 );
494 }
495 self.all_ran = true;
496 }
497
498 #[inline]
501 fn eval_all(&mut self) {
502 let fresh = self.drive.stale;
503 if fresh {
504 self.begin_epoch();
505 } else {
506 self.refresh_cells();
507 }
508 if fresh && !self.use_clean && !self.any_none {
509 self.run_guarded(|core| core.run_fresh());
510 } else {
511 let all = std::sync::Arc::clone(&self.all);
512 self.run_steps(&all);
513 }
514 }
515
516 fn pull_named(&mut self, name: &str) -> crate::ast::Value {
518 if self.drive.stale {
519 self.begin_epoch();
520 } else {
521 self.refresh_cells();
522 }
523 let plan = std::sync::Arc::clone(&self.plan);
524 if let Some(order) = plan.cones.get(name) {
525 self.run_steps(order);
526 }
527 self.value_of(name)
528 }
529
530 fn pull_at(&mut self, index: usize) -> crate::ast::Value {
534 if self.resolved_outputs.len() <= index {
535 self.resolved_outputs.resize(index + 1, None);
536 }
537 if self.resolved_outputs[index].is_none() {
538 let name = self
539 .externs
540 .output_names()
541 .get(index)
542 .cloned()
543 .unwrap_or_else(|| {
544 panic!(
545 "no output at index {index}; this kernel declares {}",
546 self.externs.output_names().len()
547 )
548 });
549 let slot = self.output_map[&name];
550 let ty = self
551 .output_types
552 .get(&name)
553 .copied()
554 .unwrap_or(crate::ast::PortType::U64);
555 let cone = self
556 .plan
557 .cones
558 .get(&name)
559 .map(|c| std::sync::Arc::from(c.as_slice()));
560 self.resolved_outputs[index] = Some((slot, ty, cone));
561 }
562 if self.drive.stale {
563 self.begin_epoch();
564 } else {
565 self.refresh_cells();
566 }
567 let (slot, ty, cone) = self.resolved_outputs[index]
568 .clone()
569 .expect("resolved above");
570 if let Some(order) = cone {
571 self.run_steps(&order);
572 }
573 self.slot_value(slot, ty)
574 }
575
576 fn value_of(&self, name: &str) -> crate::ast::Value {
579 let slot = self.output_map[name];
580 let ty = self
581 .output_types
582 .get(name)
583 .copied()
584 .unwrap_or(crate::ast::PortType::U64);
585 self.slot_value(slot, ty)
586 }
587
588 fn slot_value(&self, slot: usize, ty: crate::ast::PortType) -> crate::ast::Value {
591 if self.none.get(slot).copied().unwrap_or(false) {
592 return crate::ast::Value::None;
593 }
594 crate::compile::marshal::decode_output(&self.buffer, slot, ty)
595 }
596
597 fn plan(&self) -> crate::EnginePlan {
599 let (native_segments, closure_steps) = self.engine_counts();
600 crate::EnginePlan {
601 native_segments,
602 closure_steps,
603 interpreted_nodes: 0,
604 }
605 }
606
607 fn invalidate_all(&mut self) {
609 self.clean.fill(false);
610 self.all_ran = false;
611 self.drive.stale = true;
612 }
613}
614
615#[inline]
618fn eval_all_hybrid_steps(core: &mut HybridCore) {
619 core.drive.stale = true;
620 core.eval_all();
621}
622
623impl HybridCore {
628 fn engine_counts(&self) -> (usize, usize) {
631 let closures = self
632 .steps
633 .iter()
634 .filter(|s| matches!(s, HybridStep::Closure(_)))
635 .count();
636 (self.steps.len() - closures, closures)
637 }
638
639 fn set_extern(&mut self, name: &str, value: crate::ast::Value) -> Result<usize, String> {
644 let (slot, unset) = self.externs.set(name, value, &mut self.buffer)?;
645 self.extern_written(slot, unset);
646 Ok(slot)
647 }
648
649 fn set_extern_at(&mut self, index: usize, value: crate::ast::Value) -> Result<usize, String> {
651 let (slot, unset) = self.externs.set_at(index, value, &mut self.buffer)?;
652 self.extern_written(slot, unset);
653 Ok(slot)
654 }
655
656 fn extern_written(&mut self, slot: usize, unset: bool) {
662 self.none[slot] = unset;
663 let was = self.any_none;
664 self.any_none = self.externs.any_unset();
665 if was && !self.any_none {
666 self.none.fill(false);
667 }
668 self.dirty_input(slot);
669 self.drive.stale = true;
670 }
671}
672
673#[derive(Clone)]
678pub struct HybridKernelRaw {
679 core: HybridCore,
680}
681
682impl HybridKernelRaw {
683 #[inline]
686 fn set_coords(&mut self, coords: &[u64]) {
687 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
688 if self.core.buffer[i] != c {
689 self.core.buffer[i] = c;
690 self.core.dirty_input(i);
691 }
692 }
693 }
694
695 #[inline]
697 pub fn eval(&mut self, coords: &[u64]) {
698 self.set_coords(coords);
699 eval_all_hybrid_steps(&mut self.core);
700 }
701
702 #[cfg(feature = "jit")]
703 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
704 self.core.pull_named(name)
705 }
706
707 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
711 self.core.set_extern(name, value).map(|_| ())
712 }
713
714 pub fn set_input_at(&mut self, index: usize, value: crate::ast::Value) -> Result<(), String> {
716 self.core.set_extern_at(index, value).map(|_| ())
717 }
718
719 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
721 self.core.externs.names()
722 }
723
724 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
728 self.core.externs.cursor_schemas()
729 }
730
731 pub fn set_cursor(
735 &mut self,
736 name: &str,
737 partition: &crate::iteration::cursor_partition::Partition,
738 ) -> Result<(), String> {
739 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
740 self.set_input(&slot, value)?;
741 }
742 Ok(())
743 }
744
745 #[inline]
747 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
748 self.core.guard_ref_slot(slot);
749 self.eval(coords);
750 self.core.buffer[slot]
751 }
752
753 #[inline]
756 pub fn get(&self, name: &str) -> u64 {
757 let slot = self.core.output_map[name];
758 self.core.guard_ref_slot(slot);
759 self.core.buffer[slot]
760 }
761
762 #[inline]
765 pub fn get_slot(&self, slot: usize) -> u64 {
766 self.core.guard_ref_slot(slot);
767 self.core.buffer[slot]
768 }
769
770 crate::compile::ref_readers!();
771
772 pub fn get_value(&self, name: &str) -> crate::ast::Value {
776 self.core.value_of(name)
777 }
778
779 pub fn coord_count(&self) -> usize {
781 self.core.coord_count
782 }
783
784 pub fn engine_counts(&self) -> (usize, usize) {
787 self.core.engine_counts()
788 }
789
790 pub fn resolve_output(&self, name: &str) -> Option<usize> {
792 self.core.output_map.get(name).copied()
793 }
794
795 pub fn retain_nodes(&mut self, nodes: Vec<Box<dyn PolydatNode>>) {
797 self.core._nodes = std::sync::Arc::new(nodes);
798 }
799}
800
801#[derive(Clone)]
813pub struct HybridKernelPull {
814 core: HybridCore,
815 slot_provenance: Vec<crate::kernel::ProvMask>,
816 changed_mask: crate::kernel::ProvMask,
817 force_run: bool,
820}
821
822impl HybridKernelPull {
823 #[inline]
826 fn set_inputs(&mut self, coords: &[u64]) {
827 self.changed_mask.clear();
828 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
829 if self.core.buffer[i] != c {
830 self.core.buffer[i] = c;
831 self.changed_mask.set(i);
832 self.core.dirty_input(i);
833 }
834 }
835 }
836
837 #[inline]
839 pub fn eval(&mut self, coords: &[u64]) {
840 self.set_inputs(coords);
841 self.force_run = false;
842 eval_all_hybrid_steps(&mut self.core);
843 }
844
845 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
846 self.core.pull_named(name)
847 }
848
849 #[inline]
852 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
853 self.core.guard_ref_slot(slot);
854 self.set_inputs(coords);
855 if !self.force_run
856 && slot < self.slot_provenance.len()
857 && !self.slot_provenance[slot].intersects(&self.changed_mask)
858 {
859 return self.core.buffer[slot];
860 }
861 self.force_run = false;
862 eval_all_hybrid_steps(&mut self.core);
863 self.core.buffer[slot]
864 }
865
866 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
871 self.core.set_extern(name, value)?;
872 self.force_run = true;
873 Ok(())
874 }
875
876 pub fn set_input_at(&mut self, index: usize, value: crate::ast::Value) -> Result<(), String> {
878 self.core.set_extern_at(index, value)?;
879 self.force_run = true;
880 Ok(())
881 }
882
883 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
885 self.core.externs.names()
886 }
887
888 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
892 self.core.externs.cursor_schemas()
893 }
894
895 pub fn set_cursor(
899 &mut self,
900 name: &str,
901 partition: &crate::iteration::cursor_partition::Partition,
902 ) -> Result<(), String> {
903 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
904 self.set_input(&slot, value)?;
905 }
906 Ok(())
907 }
908
909 #[inline]
912 pub fn get(&self, name: &str) -> u64 {
913 let slot = self.core.output_map[name];
914 self.core.guard_ref_slot(slot);
915 self.core.buffer[slot]
916 }
917
918 #[inline]
921 pub fn get_slot(&self, slot: usize) -> u64 {
922 self.core.guard_ref_slot(slot);
923 self.core.buffer[slot]
924 }
925
926 crate::compile::ref_readers!();
927
928 pub fn get_value(&self, name: &str) -> crate::ast::Value {
932 self.core.value_of(name)
933 }
934
935 pub fn coord_count(&self) -> usize {
937 self.core.coord_count
938 }
939
940 pub fn engine_counts(&self) -> (usize, usize) {
943 self.core.engine_counts()
944 }
945
946 pub fn resolve_output(&self, name: &str) -> Option<usize> {
948 self.core.output_map.get(name).copied()
949 }
950
951 pub fn retain_nodes(&mut self, nodes: Vec<Box<dyn PolydatNode>>) {
953 self.core._nodes = std::sync::Arc::new(nodes);
954 }
955}
956
957#[derive(Clone)]
971pub struct HybridKernelPushPull {
972 core: HybridCore,
973 slot_provenance: Vec<crate::kernel::ProvMask>,
974 changed_mask: crate::kernel::ProvMask,
975 force_run: bool,
978}
979
980impl HybridKernelPushPull {
981 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
987 self.core.set_extern(name, value)?;
988 self.force_run = true;
989 Ok(())
990 }
991
992 pub fn set_input_at(&mut self, index: usize, value: crate::ast::Value) -> Result<(), String> {
994 self.core.set_extern_at(index, value)?;
995 self.force_run = true;
996 Ok(())
997 }
998
999 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
1001 self.core.externs.names()
1002 }
1003
1004 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
1008 self.core.externs.cursor_schemas()
1009 }
1010
1011 pub fn set_cursor(
1015 &mut self,
1016 name: &str,
1017 partition: &crate::iteration::cursor_partition::Partition,
1018 ) -> Result<(), String> {
1019 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
1020 self.set_input(&slot, value)?;
1021 }
1022 Ok(())
1023 }
1024
1025 #[inline]
1027 fn set_inputs(&mut self, coords: &[u64]) {
1028 self.changed_mask.clear();
1029 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
1030 if self.core.buffer[i] != c {
1031 self.core.buffer[i] = c;
1032 self.changed_mask.set(i);
1033 self.core.dirty_input(i);
1034 }
1035 }
1036 }
1037
1038 #[inline]
1040 pub fn eval(&mut self, coords: &[u64]) {
1041 self.set_inputs(coords);
1042 self.force_run = false;
1043 self.core.drive.stale = true;
1044 self.core.eval_all();
1045 }
1046
1047 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1048 self.core.pull_named(name)
1049 }
1050
1051 #[inline]
1053 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1054 self.core.guard_ref_slot(slot);
1055 self.set_inputs(coords);
1056 if !self.force_run
1057 && slot < self.slot_provenance.len()
1058 && !self.slot_provenance[slot].intersects(&self.changed_mask)
1059 {
1060 return self.core.buffer[slot];
1061 }
1062 self.force_run = false;
1063 self.core.drive.stale = true;
1064 self.core.eval_all();
1065 self.core.buffer[slot]
1066 }
1067
1068 #[inline]
1071 pub fn get(&self, name: &str) -> u64 {
1072 let slot = self.core.output_map[name];
1073 self.core.guard_ref_slot(slot);
1074 self.core.buffer[slot]
1075 }
1076
1077 #[inline]
1080 pub fn get_slot(&self, slot: usize) -> u64 {
1081 self.core.guard_ref_slot(slot);
1082 self.core.buffer[slot]
1083 }
1084
1085 crate::compile::ref_readers!();
1086
1087 pub fn get_value(&self, name: &str) -> crate::ast::Value {
1091 self.core.value_of(name)
1092 }
1093
1094 pub fn coord_count(&self) -> usize {
1096 self.core.coord_count
1097 }
1098
1099 pub fn engine_counts(&self) -> (usize, usize) {
1102 self.core.engine_counts()
1103 }
1104
1105 pub fn resolve_output(&self, name: &str) -> Option<usize> {
1107 self.core.output_map.get(name).copied()
1108 }
1109
1110 pub fn retain_nodes(&mut self, nodes: Vec<Box<dyn PolydatNode>>) {
1112 self.core._nodes = std::sync::Arc::new(nodes);
1113 }
1114}
1115
1116pub type HybridKernel = HybridKernelPushPull;
1122
1123fn flatten_input_slots(
1127 wiring: &[Vec<WireSource>],
1128 nodes: &[Box<dyn PolydatNode>],
1129 node_idx: usize,
1130 port_offsets: &[Vec<usize>],
1131 input_starts: &[usize],
1132 input_widths: &[usize],
1133) -> Vec<usize> {
1134 let mut slots = Vec::new();
1135 for source in &wiring[node_idx] {
1136 let (start, w) = match source {
1137 WireSource::Input(c) => (
1138 input_starts.get(*c).copied().unwrap_or(*c),
1139 input_widths.get(*c).copied().unwrap_or(1),
1140 ),
1141 WireSource::NodeOutput(u, p) => (
1142 port_offsets[*u][*p],
1143 nodes[*u].meta().outs[*p].typ.slot_width(),
1144 ),
1145 };
1146 slots.extend(start..start + w);
1147 }
1148 slots
1149}
1150
1151fn flatten_ref_output_starts(
1154 nodes: &[Box<dyn PolydatNode>],
1155 node_idx: usize,
1156 port_offsets: &[Vec<usize>],
1157) -> Vec<usize> {
1158 nodes[node_idx]
1159 .meta()
1160 .outs
1161 .iter()
1162 .enumerate()
1163 .filter(|(_, out)| out.typ.slot_color() == crate::ast::SlotColor::Ref2)
1164 .map(|(p, _)| port_offsets[node_idx][p])
1165 .collect()
1166}
1167
1168fn flatten_output_slots(
1170 nodes: &[Box<dyn PolydatNode>],
1171 node_idx: usize,
1172 port_offsets: &[Vec<usize>],
1173) -> Vec<usize> {
1174 let mut slots = Vec::new();
1175 for (p, out) in nodes[node_idx].meta().outs.iter().enumerate() {
1176 let start = port_offsets[node_idx][p];
1177 slots.extend(start..start + out.typ.slot_width());
1178 }
1179 slots
1180}
1181
1182#[cfg(feature = "jit")]
1190#[allow(clippy::too_many_arguments)]
1191pub(crate) fn build_hybrid(
1192 nodes: &[Box<dyn PolydatNode>],
1193 wiring: &[Vec<WireSource>],
1194 coord_count: usize,
1195 total_slots: usize,
1196 port_offsets: &[Vec<usize>],
1197 input_starts: &[usize],
1198 input_widths: &[usize],
1199 output_map: HashMap<String, usize>,
1200 ref_slots: Vec<bool>,
1201 input_types: &[crate::ast::PortType],
1202 externs: crate::compile::externs::Externs,
1203 constant: Vec<bool>,
1204 volatile: Vec<bool>,
1205 attribution: std::sync::Arc<crate::compile::Attribution>,
1206) -> Result<HybridKernelPushPull, String> {
1207 let mut steps: Vec<HybridStep> = Vec::new();
1208 let mut scratch: Vec<crate::ast::ScratchBuf> = Vec::new();
1209 let mut ref_scratch: Vec<(usize, usize)> = Vec::new();
1210 let mut max_inputs = 0usize;
1211 let mut max_outputs = 0usize;
1212
1213 let classifications: Vec<(JitOp, Vec<usize>, Vec<usize>)> = nodes
1215 .iter()
1216 .enumerate()
1217 .map(|(node_idx, node)| {
1218 let wire_types: Vec<crate::ast::PortType> = wiring[node_idx]
1222 .iter()
1223 .map(|src| match src {
1224 WireSource::Input(c) => input_types
1225 .get(*c)
1226 .copied()
1227 .unwrap_or(crate::ast::PortType::U64),
1228 WireSource::NodeOutput(j, p) => nodes[*j].meta().outs[*p].typ,
1229 })
1230 .collect();
1231 let jit_op = jit::classify_node_typed(node.as_ref(), &wire_types);
1232
1233 let input_slots = flatten_input_slots(
1234 wiring,
1235 nodes,
1236 node_idx,
1237 port_offsets,
1238 input_starts,
1239 input_widths,
1240 );
1241 let output_slots = flatten_output_slots(nodes, node_idx, port_offsets);
1242
1243 max_inputs = max_inputs.max(input_slots.len());
1244 max_outputs = max_outputs.max(output_slots.len());
1245
1246 (jit_op, input_slots, output_slots)
1247 })
1248 .collect();
1249 let mut classifications = classifications;
1254 let unset = externs.unset_slots();
1255 if !unset.is_empty() {
1256 let mut tainted = vec![false; nodes.len()];
1257 for node_idx in 0..nodes.len() {
1258 tainted[node_idx] = wiring[node_idx].iter().any(|src| match src {
1259 WireSource::Input(c) => unset.contains(&input_starts[*c]),
1260 WireSource::NodeOutput(j, _) => tainted[*j],
1261 });
1262 if tainted[node_idx] {
1263 classifications[node_idx].0 = JitOp::Fallback;
1264 }
1265 }
1266 }
1267
1268 let mut node_step = vec![usize::MAX; nodes.len()];
1270 let order: Vec<usize> = (0..nodes.len())
1278 .filter(|&k| constant[k])
1279 .chain((0..nodes.len()).filter(|&k| !constant[k]))
1280 .collect();
1281 let mut pos = 0;
1283 while pos < order.len() {
1284 let i = order[pos];
1285 if matches!(classifications[i].0, JitOp::Fallback) {
1286 let node = &nodes[i];
1289 let (_, ref input_slots, ref output_slots) = classifications[i];
1290 let scratch_start = scratch.len();
1291 let wire_types: Vec<crate::ast::PortType> = wiring[i]
1292 .iter()
1293 .map(|src| match src {
1294 WireSource::Input(c) => input_types
1295 .get(*c)
1296 .copied()
1297 .unwrap_or(crate::ast::PortType::U64),
1298 WireSource::NodeOutput(j, p) => nodes[*j].meta().outs[*p].typ,
1299 })
1300 .collect();
1301 let op = if let Some(op) = node.compiled_u64() {
1302 ClosureOp::U64(op)
1303 } else if let Some(op) = crate::compile::assembly::identity_op(node.as_ref()) {
1304 ClosureOp::U64(op)
1305 } else if let Some(kit) = ref_copy_or_slot(node.as_ref(), &wire_types) {
1306 scratch.extend(kit.scratch.iter().map(|e| crate::ast::ScratchBuf::new(*e)));
1307 let starts = flatten_ref_output_starts(nodes, i, port_offsets);
1308 ref_scratch.extend(crate::compile::assembly::scratch_pairs(
1309 &node.meta().name,
1310 &starts,
1311 &kit.scratch,
1312 scratch_start,
1313 ));
1314 ClosureOp::Slot(kit.op)
1315 } else {
1316 return Err(format!(
1317 "node '{}' has no compiled form and can't be JIT-compiled",
1318 node.meta().name
1319 ));
1320 };
1321 node_step[i] = steps.len();
1322 steps.push(HybridStep::Closure(ClosureStep {
1323 op,
1324 input_slots: input_slots.clone(),
1325 output_slots: output_slots.clone(),
1326 scratch_range: (scratch_start, scratch.len()),
1327 accepts_none: node.accepts_none_inputs(),
1328 node: i,
1329 }));
1330 pos += 1;
1331 } else {
1332 let is_side =
1342 |k: usize| matches!(nodes[k].purity(), crate::ast::Purity::SideChannel { .. });
1343 let batch_start = pos;
1344 let first = order[batch_start];
1345 while pos < order.len()
1346 && !matches!(classifications[order[pos]].0, JitOp::Fallback)
1347 && constant[order[pos]] == constant[first]
1348 && volatile[order[pos]] == volatile[first]
1349 && !is_side(order[pos])
1350 && !is_side(first)
1351 {
1352 pos += 1;
1353 }
1354 if pos == batch_start {
1355 pos += 1;
1356 }
1357 let members: Vec<usize> = order[batch_start..pos].to_vec();
1358 for &k in &members {
1362 let base = scratch.len();
1363 classifications[k].0.place_scratch(base);
1364 let elems = classifications[k].0.scratch_elems().to_vec();
1365 ref_scratch.extend(crate::compile::assembly::scratch_pairs(
1366 &nodes[k].meta().name,
1367 &flatten_ref_output_starts(nodes, k, port_offsets),
1368 &elems,
1369 base,
1370 ));
1371 scratch.extend(elems.iter().map(|e| crate::ast::ScratchBuf::new(*e)));
1372 }
1373 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);
1625 let input_dependents: Vec<Vec<usize>> =
1626 crate::kernel::PolydatProgram::compute_dependents(&node_provenance, input_widths.len())
1627 .iter()
1628 .map(|d| to_steps(d))
1629 .collect();
1630 let step_dependents: Vec<Vec<usize>> = input_widths
1631 .iter()
1632 .enumerate()
1633 .flat_map(|(i, w)| {
1634 std::iter::repeat_n(input_dependents.get(i).cloned().unwrap_or_default(), *w)
1635 })
1636 .collect();
1637
1638 let step_outs: Vec<&[usize]> = steps.iter().map(|s| s.output_slots()).collect();
1639 let slot_provenance =
1640 crate::compile::slot_provenance(coord_count, total_slots, &step_outs, &step_dependents);
1641
1642 debug_assert_eq!(constant.len(), nodes.len());
1647 debug_assert_eq!(volatile.len(), nodes.len());
1648 let mut step_constant = vec![true; step_count];
1649 let mut step_volatile = vec![false; step_count];
1650 let mut side = vec![false; step_count];
1651 for (n, node) in nodes.iter().enumerate() {
1652 let s = node_step[n];
1653 step_constant[s] &= constant[n];
1654 step_volatile[s] |= volatile[n];
1655 side[s] |= matches!(node.purity(), crate::ast::Purity::SideChannel { .. });
1656 }
1657 let volatile = step_volatile;
1658 let constants: Vec<usize> = (0..step_count).filter(|&i| step_constant[i]).collect();
1659 let step_inputs: Vec<&[usize]> = steps.iter().map(|s| s.input_slots()).collect();
1660 let step_outputs: Vec<&[usize]> = steps.iter().map(|s| s.output_slots()).collect();
1661 let plan = crate::compile::Invalidation::from_provenance(
1662 step_dependents.clone(),
1663 &step_inputs,
1664 &step_outputs,
1665 &output_map,
1666 total_slots,
1667 );
1668 let mut slot_step: Vec<Option<usize>> = vec![None; total_slots];
1669 for (i, outs) in step_outputs.iter().enumerate() {
1670 for &s in outs.iter() {
1671 slot_step[s] = Some(i);
1672 }
1673 }
1674 drop(step_inputs);
1675 drop(step_outputs);
1676
1677 let dirty: Vec<Vec<usize>> = plan.input_dependents.clone();
1678 let volatile_steps: Vec<usize> = (0..step_count).filter(|&i| volatile[i]).collect();
1679 let mut kernel = HybridKernelPushPull {
1680 core: HybridCore {
1681 buffer,
1682 coord_count,
1683 steps: std::sync::Arc::new(steps),
1684 output_map,
1685 gather_buf: vec![0u64; max_inputs.max(1)],
1686 scatter_buf: vec![0u64; max_outputs.max(1)],
1687 scratch,
1688 ref_slots,
1689 ref_scratch,
1690 output_types,
1691 externs,
1692 traversals: Vec::new().into(),
1693 resolved_outputs: Vec::new(),
1694 _nodes: std::sync::Arc::new(Vec::new()),
1695 drive: crate::compile::Drive {
1696 coords: Vec::new(),
1697 stale: true,
1698 },
1699 none,
1700 ran: vec![0; step_count],
1701 epoch: 0,
1702 all_ran: false,
1703 clean: vec![false; step_count],
1704 use_clean: true,
1705 plan: std::sync::Arc::new(plan),
1706 volatile: volatile.into(),
1707 side: side.into(),
1708 slot_step: slot_step.into(),
1709 sites: attribution,
1710 cur_step: 0,
1711 tracker: total_slots,
1712 all: (0..step_count).collect::<Vec<usize>>().into(),
1713 dirty: dirty.into(),
1714 any_none,
1715 volatile_steps: volatile_steps.into(),
1716 },
1717 slot_provenance,
1718 changed_mask: crate::kernel::ProvMask::all_below(coord_count), force_run: false,
1720 };
1721 kernel.core.begin_epoch();
1726 kernel.core.run_steps(&constants);
1727 kernel.core.drive.stale = true;
1728 Ok(kernel)
1729}
1730
1731fn ref_copy_or_slot(
1735 node: &dyn PolydatNode,
1736 wire_types: &[crate::ast::PortType],
1737) -> Option<crate::ast::CompiledSlotKit> {
1738 let meta = node.meta();
1739 if (meta.name == "identity" || meta.name.starts_with("__port_"))
1740 && meta.outs.len() == 1
1741 && meta.outs[0].typ.slot_color() == crate::ast::SlotColor::Ref2
1742 {
1743 return crate::compile::assembly::ref_copy_kit(meta.outs[0].typ);
1744 }
1745 node.compiled_slot(wire_types)
1746}
1747
1748#[cfg(feature = "jit")]
1751impl HybridKernelRaw {
1752 fn mark_all_dirty(&mut self) {}
1754}
1755
1756impl HybridKernelPull {
1757 fn mark_all_dirty(&mut self) {
1759 self.changed_mask = crate::kernel::ProvMask::all_below(self.core.coord_count);
1760 self.force_run = true;
1761 }
1762}
1763
1764impl HybridKernelPushPull {
1765 fn mark_all_dirty(&mut self) {
1767 self.core.clean.fill(false);
1768 self.changed_mask = crate::kernel::ProvMask::all_below(self.core.coord_count);
1769 self.force_run = true;
1770 }
1771
1772 #[cfg(feature = "jit")]
1775 pub(crate) fn into_raw(self) -> HybridKernelRaw {
1776 let mut core = self.core;
1777 core.set_use_clean(false);
1778 HybridKernelRaw { core }
1779 }
1780
1781 #[cfg(feature = "jit")]
1783 pub(crate) fn into_pull(self) -> HybridKernelPull {
1784 let mut core = self.core;
1785 core.set_use_clean(false);
1786 let changed_mask = crate::kernel::ProvMask::all_below(core.coord_count);
1787 HybridKernelPull {
1788 core,
1789 slot_provenance: self.slot_provenance,
1790 changed_mask,
1791 force_run: false,
1792 }
1793 }
1794}
1795
1796use crate::compile::select::{Engine, Provenance};
1797
1798#[cfg(feature = "jit")]
1799crate::compile::impl_kernel_trait!(HybridKernelRaw, Engine::Native(Provenance::Raw));
1800crate::compile::impl_kernel_trait!(HybridKernelPull, Engine::Native(Provenance::Pull));
1801crate::compile::impl_kernel_trait!(HybridKernelPushPull, Engine::Native(Provenance::PushPull));
1802
1803macro_rules! hybrid_drive {
1807 ($ty:ident, $set_coords:ident) => {
1808 impl $ty {
1809 fn pull_value(&mut self, name: &str) -> crate::ast::Value {
1813 let coords = std::mem::take(&mut self.core.drive.coords);
1814 self.$set_coords(&coords);
1815 self.core.drive.coords = coords;
1816 self.pull_output(name)
1817 }
1818 fn pull_value_at(&mut self, index: usize) -> crate::ast::Value {
1820 let coords = std::mem::take(&mut self.core.drive.coords);
1821 self.$set_coords(&coords);
1822 self.core.drive.coords = coords;
1823 self.core.pull_at(index)
1824 }
1825 fn eval_pending(&mut self) {
1826 let coords = std::mem::take(&mut self.core.drive.coords);
1827 self.eval(&coords);
1828 self.core.drive.coords = coords;
1829 }
1830 }
1831 };
1832}
1833#[cfg(feature = "jit")]
1834hybrid_drive!(HybridKernelRaw, set_coords);
1835hybrid_drive!(HybridKernelPull, set_inputs);
1836hybrid_drive!(HybridKernelPushPull, set_inputs);
1837
1838#[inline(always)]
1845fn run_hybrid_step(
1846 step: &HybridStep,
1847 none_free: bool,
1848 buffer: &mut [u64],
1849 none: &mut [bool],
1850 gather: &mut [u64],
1851 scatter: &mut [u64],
1852 scratch: &mut [crate::ast::ScratchBuf],
1853) {
1854 if !none_free && step.input_slots().iter().any(|&s| none[s]) {
1855 #[cfg(feature = "jit")]
1856 if let HybridStep::Jit(_) = step {
1857 panic!(
1858 "a `None` reached native code in a hybrid kernel: an extern was cleared \
1859 after the build (docs/design/engine_parity.md, A12)"
1860 );
1861 }
1862 if !step.accepts_none() {
1863 for &s in step.output_slots() {
1864 none[s] = true;
1865 }
1866 return;
1867 }
1868 }
1869 match step {
1870 #[cfg(feature = "jit")]
1871 HybridStep::Jit(seg) => {
1872 let code_fn = seg.code_fn;
1876 let buf_const = buffer.as_ptr();
1877 let buf_mut = buffer.as_mut_ptr();
1878 let sc = scratch.as_mut_ptr();
1879 if seg.fallible {
1880 crate::compile::jit::invoke_with_catch(move || unsafe {
1881 (code_fn)(buf_const, buf_mut, sc);
1882 });
1883 } else {
1884 unsafe { (code_fn)(buf_const, buf_mut, sc) };
1885 }
1886 }
1887 HybridStep::Closure(cs) => {
1888 for (i, &slot) in cs.input_slots.iter().enumerate() {
1889 gather[i] = buffer[slot];
1890 }
1891 match &cs.op {
1892 ClosureOp::U64(op) => op(
1893 &gather[..cs.input_slots.len()],
1894 &mut scatter[..cs.output_slots.len()],
1895 ),
1896 ClosureOp::Slot(op) => op(
1897 &gather[..cs.input_slots.len()],
1898 &mut scatter[..cs.output_slots.len()],
1899 &mut scratch[cs.scratch_range.0..cs.scratch_range.1],
1900 ),
1901 }
1902 for (i, &slot) in cs.output_slots.iter().enumerate() {
1903 buffer[slot] = scatter[i];
1904 }
1905 }
1906 }
1907 if !none_free {
1908 for &s in step.output_slots() {
1909 none[s] = false;
1910 }
1911 }
1912}