1use std::collections::HashMap;
21
22use crate::ast::{CompiledSlotOp, CompiledU64Op, PortType, ScratchBuf, ScratchElem};
23
24#[derive(Default)]
28pub(crate) struct P2Extras {
29 pub(crate) output_types: HashMap<String, PortType>,
30 pub(crate) externs: crate::compile::externs::Externs,
33 pub(crate) input_dependents: Vec<Vec<usize>>,
36 pub(crate) attribution: std::sync::Arc<crate::compile::Attribution>,
38}
39
40pub(crate) enum StepOp {
45 U64(CompiledU64Op),
46 Slot(CompiledSlotOp),
47 Copy,
50}
51pub(crate) struct P2Step {
53 pub(crate) name: String,
55 pub(crate) op: StepOp,
56 pub(crate) input_slots: Vec<usize>,
57 pub(crate) output_slots: Vec<usize>,
58 pub(crate) scratch: Vec<ScratchElem>,
60 pub(crate) ref_output_starts: Vec<usize>,
65 pub(crate) accepts_none: bool,
68 pub(crate) volatile: bool,
71 pub(crate) constant: bool,
74 pub(crate) side: bool,
78}
79
80struct CompiledStep {
81 op: StepOp,
82 input_slots: Vec<usize>,
83 output_slots: Vec<usize>,
84 scratch_range: (usize, usize),
85 accepts_none: bool,
87 volatile: bool,
89 constant: bool,
91 side: bool,
94}
95
96type ResolvedOutput = (usize, crate::ast::PortType, Option<std::sync::Arc<[usize]>>);
99
100struct KernelCore {
106 buffer: Vec<u64>,
107 coord_count: usize,
108 steps: std::sync::Arc<[CompiledStep]>,
109 output_map: HashMap<String, usize>,
110 gather_buf: Vec<u64>,
111 scatter_buf: Vec<u64>,
112 scratch: Vec<ScratchBuf>,
116 ref_slots: Vec<bool>,
119 ref_scratch: Vec<(usize, usize)>,
122 output_types: HashMap<String, PortType>,
124 externs: crate::compile::externs::Externs,
126 traversals: std::sync::Arc<[crate::dsl::traversal::Traversal]>,
129 resolved_outputs: Vec<Option<ResolvedOutput>>,
132 drive: crate::compile::Drive,
136 none: Vec<bool>,
139 ran: Vec<u64>,
142 epoch: u64,
148 all_ran: bool,
150 clean: Vec<bool>,
154 use_clean: bool,
158 plan: std::sync::Arc<crate::compile::Invalidation>,
161 slot_step: std::sync::Arc<[Option<usize>]>,
163 sites: std::sync::Arc<crate::compile::Attribution>,
165 cur_step: usize,
167 all: std::sync::Arc<[usize]>,
169 dirty: std::sync::Arc<[Vec<usize>]>,
175 volatile_steps: std::sync::Arc<[usize]>,
177 any_none: bool,
181}
182
183impl Clone for KernelCore {
184 fn clone(&self) -> Self {
185 let mut core = KernelCore {
186 buffer: self.buffer.clone(),
187 coord_count: self.coord_count,
188 steps: self.steps.clone(),
189 output_map: self.output_map.clone(),
190 gather_buf: self.gather_buf.clone(),
191 scatter_buf: self.scatter_buf.clone(),
192 scratch: self.scratch.clone(),
193 ref_slots: self.ref_slots.clone(),
194 ref_scratch: self.ref_scratch.clone(),
195 output_types: self.output_types.clone(),
196 externs: self.externs.clone(),
197 traversals: self.traversals.clone(),
198 resolved_outputs: self.resolved_outputs.clone(),
199 drive: self.drive.clone(),
200 none: self.none.clone(),
201 ran: self.ran.clone(),
202 epoch: self.epoch,
203 all_ran: self.all_ran,
204 clean: self.clean.clone(),
205 use_clean: self.use_clean,
206 plan: self.plan.clone(),
207 slot_step: self.slot_step.clone(),
208 sites: self.sites.clone(),
209 cur_step: self.cur_step,
210 all: self.all.clone(),
211 dirty: self.dirty.clone(),
212 volatile_steps: self.volatile_steps.clone(),
213 any_none: self.any_none,
214 };
215 core.republish_refs();
216 core
217 }
218}
219
220impl KernelCore {
221 fn republish_refs(&mut self) {
226 for &(slot, idx) in &self.ref_scratch {
227 let (p, l) = self.scratch[idx].ptr_len();
228 self.buffer[slot] = p;
229 self.buffer[slot + 1] = l;
230 }
231 self.externs.seed(&mut self.buffer, None);
232 }
233}
234
235impl KernelCore {
236 #[inline]
242 fn begin_epoch(&mut self) {
243 if self.externs.cells_dirty() {
244 self.externs.refresh_cells(&mut self.buffer);
245 }
246 self.dirty_refreshed();
247 self.epoch += 1;
248 self.all_ran = false;
249 for &i in self.volatile_steps.iter() {
250 self.clean[i] = false;
251 }
252 self.drive.stale = false;
253 }
254
255 #[inline]
260 fn dirty_refreshed(&mut self) {
261 if !self.externs.has_changed() {
262 return;
263 }
264 let changed = self.externs.take_changed();
265 for &slot in &changed {
266 if let Some(deps) = self.plan.input_dependents.get(slot) {
267 for &i in deps {
268 self.ran[i] = 0;
269 self.clean[i] = false;
270 }
271 self.all_ran = false;
272 }
273 }
274 self.externs.return_changed(changed);
275 }
276
277 #[inline]
281 fn refresh_cells(&mut self) {
282 if self.externs.cells_dirty() {
283 self.externs.refresh_cells(&mut self.buffer);
284 self.dirty_refreshed();
285 }
286 }
287
288 fn attach_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> Result<(), String> {
291 let slot = self.externs.attach_cell(name, cell)?;
292 self.dirty_input(slot);
293 self.drive.stale = true;
294 Ok(())
295 }
296
297 #[inline]
300 fn dirty_input(&mut self, slot: usize) {
301 if let Some(deps) = self.dirty.get(slot) {
302 for &i in deps {
303 self.clean[i] = false;
304 }
305 }
306 }
307
308 #[inline]
312 fn run_steps(&mut self, order: &[usize]) {
313 self.run_guarded(|core| core.run_order(order));
314 }
315
316 #[inline]
320 fn run_guarded(&mut self, body: impl FnOnce(&mut Self)) {
321 let capture = crate::kernel::engines::EvalPanicCaptureGuard::arm();
322 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| body(self)));
323 drop(capture);
324 if let Err(payload) = outcome {
325 let sites = std::sync::Arc::clone(&self.sites);
326 sites.reraise(payload, self.cur_step, &self.buffer, Some(&self.none));
327 }
328 #[cfg(debug_assertions)]
329 self.validate_refs();
330 }
331
332 #[inline]
334 fn run_order(&mut self, order: &[usize]) {
335 let steps = &self.steps;
336 let none_free = !self.any_none;
337 for &i in order {
338 if self.all_ran || self.ran[i] == self.epoch {
339 continue;
340 }
341 let step = &steps[i];
342 if (self.use_clean || step.side) && self.clean[i] && !step.volatile {
345 self.ran[i] = self.epoch;
346 continue;
347 }
348 self.cur_step = i;
349 if none_free {
350 run_step_fast(
351 step,
352 &mut self.buffer,
353 &mut self.gather_buf,
354 &mut self.scatter_buf,
355 &mut self.scratch,
356 );
357 } else {
358 run_step(
359 step,
360 &mut self.buffer,
361 &mut self.none,
362 &mut self.gather_buf,
363 &mut self.scatter_buf,
364 &mut self.scratch,
365 );
366 }
367 self.ran[i] = self.epoch;
368 self.clean[i] = !step.volatile;
369 }
370 }
371
372 #[inline]
378 fn run_fresh(&mut self) {
379 let steps = &self.steps;
380 for (i, step) in steps.iter().enumerate() {
381 if step.side {
382 if self.clean[i] && !step.volatile {
383 continue;
384 }
385 self.clean[i] = !step.volatile;
386 }
387 self.cur_step = i;
388 run_step_fast(
389 step,
390 &mut self.buffer,
391 &mut self.gather_buf,
392 &mut self.scatter_buf,
393 &mut self.scratch,
394 );
395 }
396 self.all_ran = true;
397 }
398
399 #[inline]
402 fn eval_all(&mut self) {
403 let fresh = self.drive.stale;
404 if fresh {
405 self.begin_epoch();
406 } else {
407 self.refresh_cells();
408 }
409 if fresh && !self.use_clean && !self.any_none {
410 self.run_guarded(|core| core.run_fresh());
411 } else {
412 let all = std::sync::Arc::clone(&self.all);
413 self.run_steps(&all);
414 }
415 }
416
417 fn pull_named(&mut self, name: &str) -> crate::ast::Value {
420 if self.drive.stale {
421 self.begin_epoch();
422 } else {
423 self.refresh_cells();
424 }
425 let plan = std::sync::Arc::clone(&self.plan);
426 if let Some(order) = plan.cones.get(name) {
427 self.run_steps(order);
428 }
429 self.value_of(name)
430 }
431
432 fn pull_at(&mut self, index: usize) -> crate::ast::Value {
436 if self.resolved_outputs.len() <= index {
437 self.resolved_outputs.resize(index + 1, None);
438 }
439 if self.resolved_outputs[index].is_none() {
440 let name = self
441 .externs
442 .output_names()
443 .get(index)
444 .cloned()
445 .unwrap_or_else(|| {
446 panic!(
447 "no output at index {index}; this kernel declares {}",
448 self.externs.output_names().len()
449 )
450 });
451 let slot = self.output_map[&name];
452 let ty = self
453 .output_types
454 .get(&name)
455 .copied()
456 .unwrap_or(crate::ast::PortType::U64);
457 let cone = self
458 .plan
459 .cones
460 .get(&name)
461 .map(|c| std::sync::Arc::from(c.as_slice()));
462 self.resolved_outputs[index] = Some((slot, ty, cone));
463 }
464 if self.drive.stale {
465 self.begin_epoch();
466 } else {
467 self.refresh_cells();
468 }
469 let (slot, ty, cone) = self.resolved_outputs[index]
470 .clone()
471 .expect("resolved above");
472 if let Some(order) = cone {
473 self.run_steps(&order);
474 }
475 self.slot_value(slot, ty)
476 }
477
478 fn value_of(&self, name: &str) -> crate::ast::Value {
481 let slot = self.output_map[name];
482 let ty = self
483 .output_types
484 .get(name)
485 .copied()
486 .unwrap_or(crate::ast::PortType::U64);
487 self.slot_value(slot, ty)
488 }
489
490 fn slot_value(&self, slot: usize, ty: crate::ast::PortType) -> crate::ast::Value {
493 if self.none.get(slot).copied().unwrap_or(false) {
494 return crate::ast::Value::None;
495 }
496 crate::compile::marshal::decode_output(&self.buffer, slot, ty)
497 }
498
499 fn plan(&self) -> crate::EnginePlan {
501 crate::EnginePlan {
502 closure_steps: self.steps.len(),
503 ..Default::default()
504 }
505 }
506
507 fn invalidate_all(&mut self) {
509 self.clean.fill(false);
510 self.all_ran = false;
511 self.drive.stale = true;
512 }
513
514 fn set_extern(&mut self, name: &str, value: crate::ast::Value) -> Result<usize, String> {
518 let (slot, unset) = self.externs.set(name, value, &mut self.buffer)?;
519 self.extern_written(slot, unset);
520 Ok(slot)
521 }
522
523 fn set_extern_at(&mut self, index: usize, value: crate::ast::Value) -> Result<usize, String> {
525 let (slot, unset) = self.externs.set_at(index, value, &mut self.buffer)?;
526 self.extern_written(slot, unset);
527 Ok(slot)
528 }
529
530 fn extern_written(&mut self, slot: usize, unset: bool) {
536 self.none[slot] = unset;
537 let was = self.any_none;
538 self.any_none = self.externs.any_unset();
539 if was && !self.any_none {
540 self.none.fill(false);
541 }
542 self.dirty_input(slot);
543 self.drive.stale = true;
544 }
545
546 #[cfg(debug_assertions)]
553 fn validate_refs(&self) {
554 for &(slot, idx) in &self.ref_scratch {
555 if let Some(Some(step)) = self.slot_step.get(slot)
558 && (self.ran[*step] == 0 || self.none[slot])
559 {
560 continue;
561 }
562 let (p, l) = self.scratch[idx].ptr_len();
563 assert!(
564 self.buffer[slot] == p && self.buffer[slot + 1] == l,
565 "S9 ref-validator: slot pair ({slot}, {}) = ({:#x}, {}) \
566 does not match scratch[{idx}] = ({p:#x}, {l}) — a slot \
567 op failed to republish or wrote the wrong slots",
568 slot + 1,
569 self.buffer[slot],
570 self.buffer[slot + 1],
571 );
572 }
573 }
574
575 #[inline]
577 fn guard_ref_slot(&self, slot: usize) {
578 if self.ref_slots.get(slot).copied().unwrap_or(false) {
579 panic!(
580 "S2 pointer containment: slot {slot} is Ref2-colored; raw u64 readers \
581 would leak an interior address. Use the typed borrow-checked accessor \
582 (read_vec_*), the boundary decode, or copy out."
583 );
584 }
585 }
586
587 fn ref_entry(&self, slot: usize) -> &ScratchBuf {
593 match self.ref_scratch.iter().find(|(s, _)| *s == slot) {
594 Some(&(_, idx)) => &self.scratch[idx],
595 None if self.ref_slots.get(slot).copied().unwrap_or(false) => panic!(
596 "slot {slot} is a Ref pair owned by the CALLER (a kernel \
597 input) — read it on the caller side"
598 ),
599 None => panic!("slot {slot} is not a Ref2-colored slot"),
600 }
601 }
602}
603
604fn build_core(
607 coord_count: usize,
608 total_slots: usize,
609 steps: Vec<P2Step>,
610 output_map: HashMap<String, usize>,
611 ref_slots: Vec<bool>,
612 extras: P2Extras,
613 use_clean: bool,
614) -> KernelCore {
615 let P2Extras {
616 output_types,
617 externs,
618 input_dependents,
619 attribution,
620 } = extras;
621 let max_inputs = steps.iter().map(|s| s.input_slots.len()).max().unwrap_or(0);
622 let max_outputs = steps
623 .iter()
624 .map(|s| s.output_slots.len())
625 .max()
626 .unwrap_or(0);
627 let mut scratch: Vec<ScratchBuf> = Vec::new();
628 let mut ref_scratch: Vec<(usize, usize)> = Vec::new();
629 let compiled_steps: Vec<CompiledStep> = steps
630 .into_iter()
631 .map(|step| {
632 let start = scratch.len();
633 scratch.extend(step.scratch.iter().map(|e| ScratchBuf::new(*e)));
634 ref_scratch.extend(crate::compile::assembly::scratch_pairs(
635 &step.name,
636 &step.ref_output_starts,
637 &step.scratch,
638 start,
639 ));
640 CompiledStep {
641 op: step.op,
642 input_slots: step.input_slots,
643 output_slots: step.output_slots,
644 scratch_range: (start, scratch.len()),
645 accepts_none: step.accepts_none,
646 volatile: step.volatile,
647 constant: step.constant,
648 side: step.side,
649 }
650 })
651 .collect();
652 let mut slot_step: Vec<Option<usize>> = vec![None; total_slots];
653 for (i, step) in compiled_steps.iter().enumerate() {
654 for &s in &step.output_slots {
655 slot_step[s] = Some(i);
656 }
657 }
658 let step_inputs: Vec<&[usize]> = compiled_steps
659 .iter()
660 .map(|s| s.input_slots.as_slice())
661 .collect();
662 let step_outputs: Vec<&[usize]> = compiled_steps
663 .iter()
664 .map(|s| s.output_slots.as_slice())
665 .collect();
666 let plan = crate::compile::Invalidation::from_provenance(
667 input_dependents,
668 &step_inputs,
669 &step_outputs,
670 &output_map,
671 total_slots,
672 );
673 let dirty: Vec<Vec<usize>> = plan
674 .input_dependents
675 .iter()
676 .map(|deps| {
677 if use_clean {
678 deps.clone()
679 } else {
680 deps.iter()
681 .copied()
682 .filter(|&i| compiled_steps[i].side)
683 .collect()
684 }
685 })
686 .collect();
687 let volatile_steps: Vec<usize> = (0..compiled_steps.len())
688 .filter(|&i| compiled_steps[i].volatile)
689 .collect();
690 let mut buffer = vec![0u64; total_slots];
691 let mut none = vec![false; total_slots];
692 let any_none = externs.seed(&mut buffer, Some(&mut none));
693 let step_count = compiled_steps.len();
694 let constants: Vec<usize> = compiled_steps
695 .iter()
696 .enumerate()
697 .filter(|(_, s)| s.constant)
698 .map(|(i, _)| i)
699 .collect();
700 let mut core = KernelCore {
701 buffer,
702 coord_count,
703 steps: compiled_steps.into(),
704 output_map,
705 gather_buf: vec![0u64; max_inputs],
706 scatter_buf: vec![0u64; max_outputs],
707 scratch,
708 ref_slots,
709 ref_scratch,
710 output_types,
711 externs,
712 traversals: Vec::new().into(),
713 resolved_outputs: Vec::new(),
714 drive: crate::compile::Drive {
715 coords: Vec::new(),
716 stale: true,
717 },
718 none,
719 ran: vec![0; step_count],
720 epoch: 0,
721 all_ran: false,
722 clean: vec![false; step_count],
723 use_clean,
724 plan: std::sync::Arc::new(plan),
725 slot_step: slot_step.into(),
726 sites: attribution,
727 cur_step: 0,
728 all: (0..step_count).collect::<Vec<usize>>().into(),
729 dirty: dirty.into(),
730 volatile_steps: volatile_steps.into(),
731 any_none,
732 };
733 core.begin_epoch();
738 core.run_steps(&constants);
739 core.drive.stale = true;
740 core
741}
742
743fn compute_slot_provenance(
746 coord_count: usize,
747 total_slots: usize,
748 input_dependents: &[Vec<usize>],
749 steps: &[CompiledStep],
750) -> Vec<crate::kernel::ProvMask> {
751 let outs: Vec<&[usize]> = steps.iter().map(|s| s.output_slots.as_slice()).collect();
752 crate::compile::slot_provenance(coord_count, total_slots, &outs, input_dependents)
753}
754
755macro_rules! kernel_accessors {
758 () => {
759 pub fn coord_count(&self) -> usize {
761 self.core.coord_count
762 }
763
764 pub fn resolve_output(&self, name: &str) -> Option<usize> {
766 self.core.output_map.get(name).copied()
767 }
768
769 #[inline]
772 pub fn get_slot(&self, slot: usize) -> u64 {
773 self.core.guard_ref_slot(slot);
774 self.core.buffer[slot]
775 }
776
777 #[inline]
780 pub fn get(&self, name: &str) -> u64 {
781 let slot = self.core.output_map[name];
782 self.core.guard_ref_slot(slot);
783 self.core.buffer[slot]
784 }
785
786 pub fn get_value(&self, name: &str) -> crate::ast::Value {
791 self.core.value_of(name)
792 }
793
794 fn pull_value(&mut self, name: &str) -> crate::ast::Value {
798 let coords = std::mem::take(&mut self.core.drive.coords);
799 self.set_coords(&coords);
800 self.core.drive.coords = coords;
801 self.pull_output(name)
802 }
803
804 fn pull_value_at(&mut self, index: usize) -> crate::ast::Value {
806 let coords = std::mem::take(&mut self.core.drive.coords);
807 self.set_coords(&coords);
808 self.core.drive.coords = coords;
809 self.core.pull_at(index)
810 }
811
812 fn eval_pending(&mut self) {
815 let coords = std::mem::take(&mut self.core.drive.coords);
816 self.eval(&coords);
817 self.core.drive.coords = coords;
818 }
819
820 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
825 let slot = self.core.set_extern(name, value)?;
826 self.mark_input_changed(slot);
827 Ok(())
828 }
829
830 pub fn set_input_at(
832 &mut self,
833 index: usize,
834 value: crate::ast::Value,
835 ) -> Result<(), String> {
836 let slot = self.core.set_extern_at(index, value)?;
837 self.mark_input_changed(slot);
838 Ok(())
839 }
840
841 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
843 self.core.externs.names()
844 }
845
846 fn mark_all_dirty(&mut self) {
850 for i in 0..self.core.coord_count {
851 self.mark_input_changed(i);
852 }
853 }
854
855 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
859 self.core.externs.cursor_schemas()
860 }
861
862 pub fn set_cursor(
866 &mut self,
867 name: &str,
868 partition: &crate::iteration::cursor_partition::Partition,
869 ) -> Result<(), String> {
870 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
871 self.set_input(&slot, value)?;
872 }
873 Ok(())
874 }
875
876 crate::compile::ref_readers!();
877 };
878}
879
880#[derive(Clone)]
885pub struct CompiledKernelRaw {
887 core: KernelCore,
888}
889
890impl CompiledKernelRaw {
891 pub(crate) fn new(
892 coord_count: usize,
893 total_slots: usize,
894 steps: Vec<P2Step>,
895 output_map: HashMap<String, usize>,
896 ref_slots: Vec<bool>,
897 extras: P2Extras,
898 ) -> Self {
899 Self {
900 core: build_core(
901 coord_count,
902 total_slots,
903 steps,
904 output_map,
905 ref_slots,
906 extras,
907 false,
908 ),
909 }
910 }
911
912 fn mark_input_changed(&mut self, slot: usize) {
915 self.core.dirty_input(slot);
916 }
917
918 #[inline]
921 fn set_coords(&mut self, coords: &[u64]) {
922 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
923 if self.core.buffer[i] != c {
924 self.core.buffer[i] = c;
925 self.core.dirty_input(i);
926 }
927 }
928 }
929
930 #[inline]
932 pub fn eval(&mut self, coords: &[u64]) {
933 self.set_coords(coords);
934 self.core.drive.stale = true;
935 self.core.eval_all();
936 }
937
938 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
939 self.core.pull_named(name)
940 }
941
942 #[inline]
944 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
945 self.core.guard_ref_slot(slot);
946 self.eval(coords);
947 self.core.buffer[slot]
948 }
949
950 kernel_accessors!();
951}
952
953#[derive(Clone)]
959pub struct CompiledKernelPush {
962 core: KernelCore,
963}
964
965impl CompiledKernelPush {
966 pub(crate) fn new(
967 coord_count: usize,
968 total_slots: usize,
969 steps: Vec<P2Step>,
970 output_map: HashMap<String, usize>,
971 input_dependents: Vec<Vec<usize>>,
972 ref_slots: Vec<bool>,
973 extras: P2Extras,
974 ) -> Self {
975 let _ = input_dependents;
977 Self {
978 core: build_core(
979 coord_count,
980 total_slots,
981 steps,
982 output_map,
983 ref_slots,
984 extras,
985 true,
986 ),
987 }
988 }
989
990 #[inline]
991 fn set_coords(&mut self, coords: &[u64]) {
992 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
993 if self.core.buffer[i] != c {
994 self.core.buffer[i] = c;
995 self.core.dirty_input(i);
996 }
997 }
998 }
999
1000 fn mark_input_changed(&mut self, slot: usize) {
1002 self.core.dirty_input(slot);
1003 }
1004
1005 #[inline]
1007 pub fn eval(&mut self, coords: &[u64]) {
1008 self.set_coords(coords);
1009 self.core.drive.stale = true;
1010 self.core.eval_all();
1011 }
1012
1013 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1014 self.core.pull_named(name)
1015 }
1016
1017 #[inline]
1019 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1020 self.core.guard_ref_slot(slot);
1021 self.eval(coords);
1022 self.core.buffer[slot]
1023 }
1024
1025 kernel_accessors!();
1026}
1027
1028#[derive(Clone)]
1035pub struct CompiledKernelPull {
1038 core: KernelCore,
1039 slot_provenance: Vec<crate::kernel::ProvMask>,
1040 changed_mask: crate::kernel::ProvMask,
1041 force_run: bool,
1044}
1045
1046impl CompiledKernelPull {
1047 pub(crate) fn new(
1048 coord_count: usize,
1049 total_slots: usize,
1050 steps: Vec<P2Step>,
1051 output_map: HashMap<String, usize>,
1052 input_dependents: &[Vec<usize>],
1053 ref_slots: Vec<bool>,
1054 extras: P2Extras,
1055 ) -> Self {
1056 let core = build_core(
1057 coord_count,
1058 total_slots,
1059 steps,
1060 output_map,
1061 ref_slots,
1062 extras,
1063 false,
1064 );
1065 let slot_provenance =
1066 compute_slot_provenance(coord_count, total_slots, input_dependents, &core.steps);
1067 Self {
1068 core,
1069 slot_provenance,
1070 changed_mask: crate::kernel::ProvMask::all_below(coord_count), force_run: false,
1072 }
1073 }
1074
1075 #[inline]
1078 fn set_coords(&mut self, coords: &[u64]) {
1079 self.changed_mask.clear();
1080 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
1081 if self.core.buffer[i] != c {
1082 self.core.buffer[i] = c;
1083 self.changed_mask.set(i);
1084 self.core.dirty_input(i);
1085 }
1086 }
1087 }
1088
1089 fn mark_input_changed(&mut self, slot: usize) {
1092 self.core.dirty_input(slot);
1093 self.force_run = true;
1094 }
1095
1096 #[inline]
1098 pub fn eval(&mut self, coords: &[u64]) {
1099 self.set_coords(coords);
1100 self.force_run = false;
1101 self.core.drive.stale = true;
1102 self.core.eval_all();
1103 }
1104
1105 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1106 self.core.pull_named(name)
1107 }
1108
1109 #[inline]
1112 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1113 self.core.guard_ref_slot(slot);
1114 self.set_coords(coords);
1115 if !self.force_run
1116 && slot < self.slot_provenance.len()
1117 && !self.slot_provenance[slot].intersects(&self.changed_mask)
1118 {
1119 return self.core.buffer[slot];
1120 }
1121 self.force_run = false;
1122 self.core.drive.stale = true;
1123 self.core.eval_all();
1124 self.core.buffer[slot]
1125 }
1126
1127 kernel_accessors!();
1128}
1129
1130#[derive(Clone)]
1136pub struct CompiledKernelPushPull {
1138 core: KernelCore,
1139 slot_provenance: Vec<crate::kernel::ProvMask>,
1140 changed_mask: crate::kernel::ProvMask,
1141 force_run: bool,
1144}
1145
1146impl CompiledKernelPushPull {
1147 pub(crate) fn new(
1148 coord_count: usize,
1149 total_slots: usize,
1150 steps: Vec<P2Step>,
1151 output_map: HashMap<String, usize>,
1152 input_dependents: Vec<Vec<usize>>,
1153 ref_slots: Vec<bool>,
1154 extras: P2Extras,
1155 ) -> Self {
1156 let core = build_core(
1157 coord_count,
1158 total_slots,
1159 steps,
1160 output_map,
1161 ref_slots,
1162 extras,
1163 true,
1164 );
1165 let slot_provenance =
1166 compute_slot_provenance(coord_count, total_slots, &input_dependents, &core.steps);
1167 Self {
1168 core,
1169 slot_provenance,
1170 changed_mask: crate::kernel::ProvMask::all_below(coord_count),
1171 force_run: false,
1172 }
1173 }
1174
1175 #[inline]
1176 fn set_coords(&mut self, coords: &[u64]) {
1177 self.changed_mask.clear();
1178 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
1179 if self.core.buffer[i] != c {
1180 self.core.buffer[i] = c;
1181 self.changed_mask.set(i);
1182 self.core.dirty_input(i);
1183 }
1184 }
1185 }
1186
1187 fn mark_input_changed(&mut self, slot: usize) {
1190 self.core.dirty_input(slot);
1191 self.force_run = true;
1192 }
1193
1194 #[inline]
1196 pub fn eval(&mut self, coords: &[u64]) {
1197 self.set_coords(coords);
1198 self.force_run = false;
1199 self.core.drive.stale = true;
1200 self.core.eval_all();
1201 }
1202
1203 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1204 self.core.pull_named(name)
1205 }
1206
1207 #[inline]
1209 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1210 self.core.guard_ref_slot(slot);
1211 self.set_coords(coords);
1212 if !self.force_run
1213 && slot < self.slot_provenance.len()
1214 && !self.slot_provenance[slot].intersects(&self.changed_mask)
1215 {
1216 return self.core.buffer[slot];
1217 }
1218 self.force_run = false;
1219 self.core.drive.stale = true;
1220 self.core.eval_all();
1221 self.core.buffer[slot]
1222 }
1223
1224 kernel_accessors!();
1225}
1226
1227use crate::compile::select::{Engine, Provenance};
1230
1231crate::compile::impl_kernel_trait!(CompiledKernelRaw, Engine::Closures(Provenance::Raw));
1232crate::compile::impl_kernel_trait!(CompiledKernelPush, Engine::Closures(Provenance::Push));
1233crate::compile::impl_kernel_trait!(CompiledKernelPull, Engine::Closures(Provenance::Pull));
1234crate::compile::impl_kernel_trait!(
1235 CompiledKernelPushPull,
1236 Engine::Closures(Provenance::PushPull)
1237);
1238
1239#[inline(always)]
1243fn run_step(
1244 step: &CompiledStep,
1245 buffer: &mut [u64],
1246 none: &mut [bool],
1247 gather: &mut [u64],
1248 scatter: &mut [u64],
1249 scratch: &mut [ScratchBuf],
1250) {
1251 let mut any_none = false;
1252 for (i, &s) in step.input_slots.iter().enumerate() {
1253 gather[i] = buffer[s];
1254 any_none |= none[s];
1255 }
1256 if any_none && !step.accepts_none {
1257 for &s in &step.output_slots {
1258 none[s] = true;
1259 }
1260 return;
1261 }
1262 if matches!(step.op, StepOp::Copy) {
1263 for (&i, &o) in step.input_slots.iter().zip(&step.output_slots) {
1264 buffer[o] = buffer[i];
1265 none[o] = false;
1266 }
1267 return;
1268 }
1269 let (n_in, n_out) = (step.input_slots.len(), step.output_slots.len());
1270 match &step.op {
1271 StepOp::Copy => unreachable!(),
1272 StepOp::U64(op) => op(&gather[..n_in], &mut scatter[..n_out]),
1273 StepOp::Slot(op) => op(
1274 &gather[..n_in],
1275 &mut scatter[..n_out],
1276 &mut scratch[step.scratch_range.0..step.scratch_range.1],
1277 ),
1278 }
1279 for (i, &s) in step.output_slots.iter().enumerate() {
1280 buffer[s] = scatter[i];
1281 none[s] = false;
1282 }
1283}
1284
1285#[inline(always)]
1287fn run_step_fast(
1288 step: &CompiledStep,
1289 buffer: &mut [u64],
1290 gather: &mut [u64],
1291 scatter: &mut [u64],
1292 scratch: &mut [ScratchBuf],
1293) {
1294 if matches!(step.op, StepOp::Copy) {
1295 for (&i, &o) in step.input_slots.iter().zip(&step.output_slots) {
1296 buffer[o] = buffer[i];
1297 }
1298 return;
1299 }
1300 for (i, &s) in step.input_slots.iter().enumerate() {
1301 gather[i] = buffer[s];
1302 }
1303 let (n_in, n_out) = (step.input_slots.len(), step.output_slots.len());
1304 match &step.op {
1305 StepOp::Copy => unreachable!(),
1306 StepOp::U64(op) => op(&gather[..n_in], &mut scatter[..n_out]),
1307 StepOp::Slot(op) => op(
1308 &gather[..n_in],
1309 &mut scatter[..n_out],
1310 &mut scratch[step.scratch_range.0..step.scratch_range.1],
1311 ),
1312 }
1313 for (i, &s) in step.output_slots.iter().enumerate() {
1314 buffer[s] = scatter[i];
1315 }
1316}