1use std::collections::HashMap;
18
19use crate::ast::{CompiledSlotOp, CompiledU64Op, PortType, ScratchBuf, ScratchElem};
20
21#[derive(Default)]
25pub(crate) struct P2Extras {
26 pub(crate) output_types: HashMap<String, PortType>,
27 pub(crate) externs: crate::compile::externs::Externs,
30 pub(crate) input_dependents: Vec<Vec<usize>>,
33 pub(crate) attribution: std::sync::Arc<crate::compile::Attribution>,
35}
36
37pub(crate) enum StepOp {
41 U64(CompiledU64Op),
42 Slot(CompiledSlotOp),
43 Copy,
46}
47pub(crate) struct P2Step {
49 pub(crate) name: String,
51 pub(crate) op: StepOp,
52 pub(crate) input_slots: Vec<usize>,
53 pub(crate) output_slots: Vec<usize>,
54 pub(crate) scratch: Vec<ScratchElem>,
56 pub(crate) ref_output_starts: Vec<usize>,
61 pub(crate) accepts_none: bool,
64 pub(crate) volatile: bool,
67 pub(crate) constant: bool,
70 pub(crate) side: bool,
74}
75
76struct CompiledStep {
77 op: StepOp,
78 input_slots: Vec<usize>,
79 output_slots: Vec<usize>,
80 scratch_range: (usize, usize),
81 accepts_none: bool,
83 volatile: bool,
85 constant: bool,
87 side: bool,
90}
91
92type ResolvedOutput = (usize, crate::ast::PortType, Option<std::sync::Arc<[usize]>>);
95
96struct KernelCore {
102 buffer: Vec<u64>,
103 coord_count: usize,
104 steps: std::sync::Arc<[CompiledStep]>,
105 output_map: HashMap<String, usize>,
106 gather_buf: Vec<u64>,
107 scatter_buf: Vec<u64>,
108 scratch: Vec<ScratchBuf>,
111 ref_slots: Vec<bool>,
114 ref_scratch: Vec<(usize, usize)>,
117 output_types: HashMap<String, PortType>,
119 externs: crate::compile::externs::Externs,
121 traversals: std::sync::Arc<[crate::dsl::traversal::Traversal]>,
124 resolved_outputs: Vec<Option<ResolvedOutput>>,
127 drive: crate::compile::Drive,
131 none: Vec<bool>,
134 ran: Vec<u64>,
137 epoch: u64,
143 all_ran: bool,
145 clean: Vec<bool>,
149 use_clean: bool,
153 plan: std::sync::Arc<crate::compile::Invalidation>,
156 slot_step: std::sync::Arc<[Option<usize>]>,
158 sites: std::sync::Arc<crate::compile::Attribution>,
160 cur_step: usize,
162 all: std::sync::Arc<[usize]>,
164 dirty: std::sync::Arc<[Vec<usize>]>,
170 volatile_steps: std::sync::Arc<[usize]>,
172 any_none: bool,
176}
177
178impl Clone for KernelCore {
179 fn clone(&self) -> Self {
180 let mut core = KernelCore {
181 buffer: self.buffer.clone(),
182 coord_count: self.coord_count,
183 steps: self.steps.clone(),
184 output_map: self.output_map.clone(),
185 gather_buf: self.gather_buf.clone(),
186 scatter_buf: self.scatter_buf.clone(),
187 scratch: self.scratch.clone(),
188 ref_slots: self.ref_slots.clone(),
189 ref_scratch: self.ref_scratch.clone(),
190 output_types: self.output_types.clone(),
191 externs: self.externs.clone(),
192 traversals: self.traversals.clone(),
193 resolved_outputs: self.resolved_outputs.clone(),
194 drive: self.drive.clone(),
195 none: self.none.clone(),
196 ran: self.ran.clone(),
197 epoch: self.epoch,
198 all_ran: self.all_ran,
199 clean: self.clean.clone(),
200 use_clean: self.use_clean,
201 plan: self.plan.clone(),
202 slot_step: self.slot_step.clone(),
203 sites: self.sites.clone(),
204 cur_step: self.cur_step,
205 all: self.all.clone(),
206 dirty: self.dirty.clone(),
207 volatile_steps: self.volatile_steps.clone(),
208 any_none: self.any_none,
209 };
210 core.republish_refs();
211 core
212 }
213}
214
215impl KernelCore {
216 fn republish_refs(&mut self) {
221 for &(slot, idx) in &self.ref_scratch {
222 let (p, l) = self.scratch[idx].ptr_len();
223 self.buffer[slot] = p;
224 self.buffer[slot + 1] = l;
225 }
226 self.externs.seed(&mut self.buffer, None);
227 }
228}
229
230impl KernelCore {
231 #[inline]
237 fn begin_epoch(&mut self) {
238 if self.externs.cells_dirty() {
239 self.externs.refresh_cells(&mut self.buffer);
240 }
241 self.dirty_refreshed();
242 self.epoch += 1;
243 self.all_ran = false;
244 for &i in self.volatile_steps.iter() {
245 self.clean[i] = false;
246 }
247 self.drive.stale = false;
248 }
249
250 #[inline]
255 fn dirty_refreshed(&mut self) {
256 if !self.externs.has_changed() {
257 return;
258 }
259 let changed = self.externs.take_changed();
260 for &slot in &changed {
261 if let Some(deps) = self.plan.input_dependents.get(slot) {
262 for &i in deps {
263 self.ran[i] = 0;
264 self.clean[i] = false;
265 }
266 self.all_ran = false;
267 }
268 }
269 self.externs.return_changed(changed);
270 }
271
272 #[inline]
276 fn refresh_cells(&mut self) {
277 if self.externs.cells_dirty() {
278 self.externs.refresh_cells(&mut self.buffer);
279 self.dirty_refreshed();
280 }
281 }
282
283 fn attach_cell(&mut self, name: &str, cell: crate::kernel::SharedCell) -> Result<(), String> {
286 let slot = self.externs.attach_cell(name, cell)?;
287 self.dirty_input(slot);
288 self.drive.stale = true;
289 Ok(())
290 }
291
292 #[inline]
295 fn dirty_input(&mut self, slot: usize) {
296 if let Some(deps) = self.dirty.get(slot) {
297 for &i in deps {
298 self.clean[i] = false;
299 }
300 }
301 }
302
303 #[inline]
307 fn run_steps(&mut self, order: &[usize]) {
308 self.run_guarded(|core| core.run_order(order));
309 }
310
311 #[inline]
315 fn run_guarded(&mut self, body: impl FnOnce(&mut Self)) {
316 let capture = crate::kernel::engines::EvalPanicCaptureGuard::arm();
317 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| body(self)));
318 drop(capture);
319 if let Err(payload) = outcome {
320 let sites = std::sync::Arc::clone(&self.sites);
321 sites.reraise(payload, self.cur_step, &self.buffer, Some(&self.none));
322 }
323 #[cfg(debug_assertions)]
324 self.validate_refs();
325 }
326
327 #[inline]
329 fn run_order(&mut self, order: &[usize]) {
330 let steps = &self.steps;
331 let none_free = !self.any_none;
332 for &i in order {
333 if self.all_ran || self.ran[i] == self.epoch {
334 continue;
335 }
336 let step = &steps[i];
337 if (self.use_clean || step.side) && self.clean[i] && !step.volatile {
340 self.ran[i] = self.epoch;
341 continue;
342 }
343 self.cur_step = i;
344 if none_free {
345 run_step_fast(
346 step,
347 &mut self.buffer,
348 &mut self.gather_buf,
349 &mut self.scatter_buf,
350 &mut self.scratch,
351 );
352 } else {
353 run_step(
354 step,
355 &mut self.buffer,
356 &mut self.none,
357 &mut self.gather_buf,
358 &mut self.scatter_buf,
359 &mut self.scratch,
360 );
361 }
362 self.ran[i] = self.epoch;
363 self.clean[i] = !step.volatile;
364 }
365 }
366
367 #[inline]
373 fn run_fresh(&mut self) {
374 let steps = &self.steps;
375 for (i, step) in steps.iter().enumerate() {
376 if step.side {
377 if self.clean[i] && !step.volatile {
378 continue;
379 }
380 self.clean[i] = !step.volatile;
381 }
382 self.cur_step = i;
383 run_step_fast(
384 step,
385 &mut self.buffer,
386 &mut self.gather_buf,
387 &mut self.scatter_buf,
388 &mut self.scratch,
389 );
390 }
391 self.all_ran = true;
392 }
393
394 #[inline]
397 fn eval_all(&mut self) {
398 let fresh = self.drive.stale;
399 if fresh {
400 self.begin_epoch();
401 } else {
402 self.refresh_cells();
403 }
404 if fresh && !self.use_clean && !self.any_none {
405 self.run_guarded(|core| core.run_fresh());
406 } else {
407 let all = std::sync::Arc::clone(&self.all);
408 self.run_steps(&all);
409 }
410 }
411
412 fn pull_named(&mut self, name: &str) -> crate::ast::Value {
415 if self.drive.stale {
416 self.begin_epoch();
417 } else {
418 self.refresh_cells();
419 }
420 let plan = std::sync::Arc::clone(&self.plan);
421 if let Some(order) = plan.cones.get(name) {
422 self.run_steps(order);
423 }
424 self.value_of(name)
425 }
426
427 fn pull_at(&mut self, index: usize) -> crate::ast::Value {
431 if self.resolved_outputs.len() <= index {
432 self.resolved_outputs.resize(index + 1, None);
433 }
434 if self.resolved_outputs[index].is_none() {
435 let name = self
436 .externs
437 .output_names()
438 .get(index)
439 .cloned()
440 .unwrap_or_else(|| {
441 panic!(
442 "no output at index {index}; this kernel declares {}",
443 self.externs.output_names().len()
444 )
445 });
446 let slot = self.output_map[&name];
447 let ty = self
448 .output_types
449 .get(&name)
450 .copied()
451 .unwrap_or(crate::ast::PortType::U64);
452 let cone = self
453 .plan
454 .cones
455 .get(&name)
456 .map(|c| std::sync::Arc::from(c.as_slice()));
457 self.resolved_outputs[index] = Some((slot, ty, cone));
458 }
459 if self.drive.stale {
460 self.begin_epoch();
461 } else {
462 self.refresh_cells();
463 }
464 let (slot, ty, cone) = self.resolved_outputs[index]
465 .clone()
466 .expect("resolved above");
467 if let Some(order) = cone {
468 self.run_steps(&order);
469 }
470 self.slot_value(slot, ty)
471 }
472
473 fn value_of(&self, name: &str) -> crate::ast::Value {
476 let slot = self.output_map[name];
477 let ty = self
478 .output_types
479 .get(name)
480 .copied()
481 .unwrap_or(crate::ast::PortType::U64);
482 self.slot_value(slot, ty)
483 }
484
485 fn slot_value(&self, slot: usize, ty: crate::ast::PortType) -> crate::ast::Value {
488 if self.none.get(slot).copied().unwrap_or(false) {
489 return crate::ast::Value::None;
490 }
491 crate::compile::marshal::decode_output(&self.buffer, slot, ty)
492 }
493
494 fn plan(&self) -> crate::EnginePlan {
496 crate::EnginePlan {
497 closure_steps: self.steps.len(),
498 ..Default::default()
499 }
500 }
501
502 fn invalidate_all(&mut self) {
504 self.clean.fill(false);
505 self.all_ran = false;
506 self.drive.stale = true;
507 }
508
509 fn set_extern(&mut self, name: &str, value: crate::ast::Value) -> Result<usize, String> {
513 let (slot, unset) = self.externs.set(name, value, &mut self.buffer)?;
514 self.extern_written(slot, unset);
515 Ok(slot)
516 }
517
518 fn set_extern_at(&mut self, index: usize, value: crate::ast::Value) -> Result<usize, String> {
520 let (slot, unset) = self.externs.set_at(index, value, &mut self.buffer)?;
521 self.extern_written(slot, unset);
522 Ok(slot)
523 }
524
525 fn extern_written(&mut self, slot: usize, unset: bool) {
531 self.none[slot] = unset;
532 let was = self.any_none;
533 self.any_none = self.externs.any_unset();
534 if was && !self.any_none {
535 self.none.fill(false);
536 }
537 self.dirty_input(slot);
538 self.drive.stale = true;
539 }
540
541 #[cfg(debug_assertions)]
548 fn validate_refs(&self) {
549 for &(slot, idx) in &self.ref_scratch {
550 if let Some(Some(step)) = self.slot_step.get(slot)
553 && (self.ran[*step] == 0 || self.none[slot])
554 {
555 continue;
556 }
557 let (p, l) = self.scratch[idx].ptr_len();
558 assert!(
559 self.buffer[slot] == p && self.buffer[slot + 1] == l,
560 "S9 ref-validator: slot pair ({slot}, {}) = ({:#x}, {}) \
561 does not match scratch[{idx}] = ({p:#x}, {l}) — a slot \
562 op failed to republish or wrote the wrong slots",
563 slot + 1,
564 self.buffer[slot],
565 self.buffer[slot + 1],
566 );
567 }
568 }
569
570 #[inline]
573 fn guard_ref_slot(&self, slot: usize) {
574 if self.ref_slots.get(slot).copied().unwrap_or(false) {
575 panic!(
576 "S2 pointer containment: slot {slot} is Ref2-colored; raw u64 readers \
577 would leak an interior address. Use the typed borrow-checked accessor \
578 (read_vec_*), the boundary decode, or copy out."
579 );
580 }
581 }
582
583 fn ref_entry(&self, slot: usize) -> &ScratchBuf {
589 match self.ref_scratch.iter().find(|(s, _)| *s == slot) {
590 Some(&(_, idx)) => &self.scratch[idx],
591 None if self.ref_slots.get(slot).copied().unwrap_or(false) => panic!(
592 "slot {slot} is a Ref pair owned by the CALLER (a kernel \
593 input) — read it on the caller side"
594 ),
595 None => panic!("slot {slot} is not a Ref2-colored slot"),
596 }
597 }
598}
599
600fn build_core(
603 coord_count: usize,
604 total_slots: usize,
605 steps: Vec<P2Step>,
606 output_map: HashMap<String, usize>,
607 ref_slots: Vec<bool>,
608 extras: P2Extras,
609 use_clean: bool,
610) -> KernelCore {
611 let P2Extras {
612 output_types,
613 externs,
614 input_dependents,
615 attribution,
616 } = extras;
617 let max_inputs = steps.iter().map(|s| s.input_slots.len()).max().unwrap_or(0);
618 let max_outputs = steps
619 .iter()
620 .map(|s| s.output_slots.len())
621 .max()
622 .unwrap_or(0);
623 let mut scratch: Vec<ScratchBuf> = Vec::new();
624 let mut ref_scratch: Vec<(usize, usize)> = Vec::new();
625 let compiled_steps: Vec<CompiledStep> = steps
626 .into_iter()
627 .map(|step| {
628 let start = scratch.len();
629 scratch.extend(step.scratch.iter().map(|e| ScratchBuf::new(*e)));
630 ref_scratch.extend(crate::compile::assembly::scratch_pairs(
631 &step.name,
632 &step.ref_output_starts,
633 &step.scratch,
634 start,
635 ));
636 CompiledStep {
637 op: step.op,
638 input_slots: step.input_slots,
639 output_slots: step.output_slots,
640 scratch_range: (start, scratch.len()),
641 accepts_none: step.accepts_none,
642 volatile: step.volatile,
643 constant: step.constant,
644 side: step.side,
645 }
646 })
647 .collect();
648 let mut slot_step: Vec<Option<usize>> = vec![None; total_slots];
649 for (i, step) in compiled_steps.iter().enumerate() {
650 for &s in &step.output_slots {
651 slot_step[s] = Some(i);
652 }
653 }
654 let step_inputs: Vec<&[usize]> = compiled_steps
655 .iter()
656 .map(|s| s.input_slots.as_slice())
657 .collect();
658 let step_outputs: Vec<&[usize]> = compiled_steps
659 .iter()
660 .map(|s| s.output_slots.as_slice())
661 .collect();
662 let plan = crate::compile::Invalidation::from_provenance(
663 input_dependents,
664 &step_inputs,
665 &step_outputs,
666 &output_map,
667 total_slots,
668 );
669 let dirty: Vec<Vec<usize>> = plan
670 .input_dependents
671 .iter()
672 .map(|deps| {
673 if use_clean {
674 deps.clone()
675 } else {
676 deps.iter()
677 .copied()
678 .filter(|&i| compiled_steps[i].side)
679 .collect()
680 }
681 })
682 .collect();
683 let volatile_steps: Vec<usize> = (0..compiled_steps.len())
684 .filter(|&i| compiled_steps[i].volatile)
685 .collect();
686 let mut buffer = vec![0u64; total_slots];
687 let mut none = vec![false; total_slots];
688 let any_none = externs.seed(&mut buffer, Some(&mut none));
689 let step_count = compiled_steps.len();
690 let constants: Vec<usize> = compiled_steps
691 .iter()
692 .enumerate()
693 .filter(|(_, s)| s.constant)
694 .map(|(i, _)| i)
695 .collect();
696 let mut core = KernelCore {
697 buffer,
698 coord_count,
699 steps: compiled_steps.into(),
700 output_map,
701 gather_buf: vec![0u64; max_inputs],
702 scatter_buf: vec![0u64; max_outputs],
703 scratch,
704 ref_slots,
705 ref_scratch,
706 output_types,
707 externs,
708 traversals: Vec::new().into(),
709 resolved_outputs: Vec::new(),
710 drive: crate::compile::Drive {
711 coords: Vec::new(),
712 stale: true,
713 },
714 none,
715 ran: vec![0; step_count],
716 epoch: 0,
717 all_ran: false,
718 clean: vec![false; step_count],
719 use_clean,
720 plan: std::sync::Arc::new(plan),
721 slot_step: slot_step.into(),
722 sites: attribution,
723 cur_step: 0,
724 all: (0..step_count).collect::<Vec<usize>>().into(),
725 dirty: dirty.into(),
726 volatile_steps: volatile_steps.into(),
727 any_none,
728 };
729 core.begin_epoch();
734 core.run_steps(&constants);
735 core.drive.stale = true;
736 core
737}
738
739fn compute_slot_provenance(
742 coord_count: usize,
743 total_slots: usize,
744 input_dependents: &[Vec<usize>],
745 steps: &[CompiledStep],
746) -> Vec<crate::kernel::ProvMask> {
747 let outs: Vec<&[usize]> = steps.iter().map(|s| s.output_slots.as_slice()).collect();
748 crate::compile::slot_provenance(coord_count, total_slots, &outs, input_dependents)
749}
750
751macro_rules! kernel_accessors {
754 () => {
755 pub fn coord_count(&self) -> usize {
757 self.core.coord_count
758 }
759
760 pub fn resolve_output(&self, name: &str) -> Option<usize> {
762 self.core.output_map.get(name).copied()
763 }
764
765 #[inline]
768 pub fn get_slot(&self, slot: usize) -> u64 {
769 self.core.guard_ref_slot(slot);
770 self.core.buffer[slot]
771 }
772
773 #[inline]
776 pub fn get(&self, name: &str) -> u64 {
777 let slot = self.core.output_map[name];
778 self.core.guard_ref_slot(slot);
779 self.core.buffer[slot]
780 }
781
782 pub fn get_value(&self, name: &str) -> crate::ast::Value {
787 self.core.value_of(name)
788 }
789
790 fn pull_value(&mut self, name: &str) -> crate::ast::Value {
794 let coords = std::mem::take(&mut self.core.drive.coords);
795 self.set_coords(&coords);
796 self.core.drive.coords = coords;
797 self.pull_output(name)
798 }
799
800 fn pull_value_at(&mut self, index: usize) -> crate::ast::Value {
802 let coords = std::mem::take(&mut self.core.drive.coords);
803 self.set_coords(&coords);
804 self.core.drive.coords = coords;
805 self.core.pull_at(index)
806 }
807
808 fn eval_pending(&mut self) {
811 let coords = std::mem::take(&mut self.core.drive.coords);
812 self.eval(&coords);
813 self.core.drive.coords = coords;
814 }
815
816 pub fn set_input(&mut self, name: &str, value: crate::ast::Value) -> Result<(), String> {
822 let slot = self.core.set_extern(name, value)?;
823 self.mark_input_changed(slot);
824 Ok(())
825 }
826
827 pub fn set_input_at(
829 &mut self,
830 index: usize,
831 value: crate::ast::Value,
832 ) -> Result<(), String> {
833 let slot = self.core.set_extern_at(index, value)?;
834 self.mark_input_changed(slot);
835 Ok(())
836 }
837
838 pub fn externs(&self) -> Vec<(&str, crate::ast::PortType)> {
840 self.core.externs.names()
841 }
842
843 fn mark_all_dirty(&mut self) {
847 for i in 0..self.core.coord_count {
848 self.mark_input_changed(i);
849 }
850 }
851
852 pub fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
856 self.core.externs.cursor_schemas()
857 }
858
859 pub fn set_cursor(
863 &mut self,
864 name: &str,
865 partition: &crate::iteration::cursor_partition::Partition,
866 ) -> Result<(), String> {
867 for (slot, value) in self.core.externs.cursor_writes(name, partition)? {
868 self.set_input(&slot, value)?;
869 }
870 Ok(())
871 }
872
873 crate::compile::ref_readers!();
874 };
875}
876
877#[derive(Clone)]
882pub struct CompiledKernelRaw {
884 core: KernelCore,
885}
886
887impl CompiledKernelRaw {
888 pub(crate) fn new(
889 coord_count: usize,
890 total_slots: usize,
891 steps: Vec<P2Step>,
892 output_map: HashMap<String, usize>,
893 ref_slots: Vec<bool>,
894 extras: P2Extras,
895 ) -> Self {
896 Self {
897 core: build_core(
898 coord_count,
899 total_slots,
900 steps,
901 output_map,
902 ref_slots,
903 extras,
904 false,
905 ),
906 }
907 }
908
909 fn mark_input_changed(&mut self, slot: usize) {
912 self.core.dirty_input(slot);
913 }
914
915 #[inline]
918 fn set_coords(&mut self, coords: &[u64]) {
919 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
920 if self.core.buffer[i] != c {
921 self.core.buffer[i] = c;
922 self.core.dirty_input(i);
923 }
924 }
925 }
926
927 #[inline]
929 pub fn eval(&mut self, coords: &[u64]) {
930 self.set_coords(coords);
931 self.core.drive.stale = true;
932 self.core.eval_all();
933 }
934
935 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
936 self.core.pull_named(name)
937 }
938
939 #[inline]
941 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
942 self.core.guard_ref_slot(slot);
943 self.eval(coords);
944 self.core.buffer[slot]
945 }
946
947 kernel_accessors!();
948}
949
950#[derive(Clone)]
956pub struct CompiledKernelPush {
959 core: KernelCore,
960}
961
962impl CompiledKernelPush {
963 pub(crate) fn new(
964 coord_count: usize,
965 total_slots: usize,
966 steps: Vec<P2Step>,
967 output_map: HashMap<String, usize>,
968 input_dependents: Vec<Vec<usize>>,
969 ref_slots: Vec<bool>,
970 extras: P2Extras,
971 ) -> Self {
972 let _ = input_dependents;
974 Self {
975 core: build_core(
976 coord_count,
977 total_slots,
978 steps,
979 output_map,
980 ref_slots,
981 extras,
982 true,
983 ),
984 }
985 }
986
987 #[inline]
988 fn set_coords(&mut self, coords: &[u64]) {
989 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
990 if self.core.buffer[i] != c {
991 self.core.buffer[i] = c;
992 self.core.dirty_input(i);
993 }
994 }
995 }
996
997 fn mark_input_changed(&mut self, slot: usize) {
999 self.core.dirty_input(slot);
1000 }
1001
1002 #[inline]
1004 pub fn eval(&mut self, coords: &[u64]) {
1005 self.set_coords(coords);
1006 self.core.drive.stale = true;
1007 self.core.eval_all();
1008 }
1009
1010 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1011 self.core.pull_named(name)
1012 }
1013
1014 #[inline]
1016 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1017 self.core.guard_ref_slot(slot);
1018 self.eval(coords);
1019 self.core.buffer[slot]
1020 }
1021
1022 kernel_accessors!();
1023}
1024
1025#[derive(Clone)]
1032pub struct CompiledKernelPull {
1035 core: KernelCore,
1036 slot_provenance: Vec<crate::kernel::ProvMask>,
1037 changed_mask: crate::kernel::ProvMask,
1038 force_run: bool,
1041}
1042
1043impl CompiledKernelPull {
1044 pub(crate) fn new(
1045 coord_count: usize,
1046 total_slots: usize,
1047 steps: Vec<P2Step>,
1048 output_map: HashMap<String, usize>,
1049 input_dependents: &[Vec<usize>],
1050 ref_slots: Vec<bool>,
1051 extras: P2Extras,
1052 ) -> Self {
1053 let core = build_core(
1054 coord_count,
1055 total_slots,
1056 steps,
1057 output_map,
1058 ref_slots,
1059 extras,
1060 false,
1061 );
1062 let slot_provenance =
1063 compute_slot_provenance(coord_count, total_slots, input_dependents, &core.steps);
1064 Self {
1065 core,
1066 slot_provenance,
1067 changed_mask: crate::kernel::ProvMask::all_below(coord_count), force_run: false,
1069 }
1070 }
1071
1072 #[inline]
1075 fn set_coords(&mut self, coords: &[u64]) {
1076 self.changed_mask.clear();
1077 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
1078 if self.core.buffer[i] != c {
1079 self.core.buffer[i] = c;
1080 self.changed_mask.set(i);
1081 self.core.dirty_input(i);
1082 }
1083 }
1084 }
1085
1086 fn mark_input_changed(&mut self, slot: usize) {
1089 self.core.dirty_input(slot);
1090 self.force_run = true;
1091 }
1092
1093 #[inline]
1095 pub fn eval(&mut self, coords: &[u64]) {
1096 self.set_coords(coords);
1097 self.force_run = false;
1098 self.core.drive.stale = true;
1099 self.core.eval_all();
1100 }
1101
1102 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1103 self.core.pull_named(name)
1104 }
1105
1106 #[inline]
1109 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1110 self.core.guard_ref_slot(slot);
1111 self.set_coords(coords);
1112 if !self.force_run
1113 && slot < self.slot_provenance.len()
1114 && !self.slot_provenance[slot].intersects(&self.changed_mask)
1115 {
1116 return self.core.buffer[slot];
1117 }
1118 self.force_run = false;
1119 self.core.drive.stale = true;
1120 self.core.eval_all();
1121 self.core.buffer[slot]
1122 }
1123
1124 kernel_accessors!();
1125}
1126
1127#[derive(Clone)]
1133pub struct CompiledKernelPushPull {
1135 core: KernelCore,
1136 slot_provenance: Vec<crate::kernel::ProvMask>,
1137 changed_mask: crate::kernel::ProvMask,
1138 force_run: bool,
1141}
1142
1143impl CompiledKernelPushPull {
1144 pub(crate) fn new(
1145 coord_count: usize,
1146 total_slots: usize,
1147 steps: Vec<P2Step>,
1148 output_map: HashMap<String, usize>,
1149 input_dependents: Vec<Vec<usize>>,
1150 ref_slots: Vec<bool>,
1151 extras: P2Extras,
1152 ) -> Self {
1153 let core = build_core(
1154 coord_count,
1155 total_slots,
1156 steps,
1157 output_map,
1158 ref_slots,
1159 extras,
1160 true,
1161 );
1162 let slot_provenance =
1163 compute_slot_provenance(coord_count, total_slots, &input_dependents, &core.steps);
1164 Self {
1165 core,
1166 slot_provenance,
1167 changed_mask: crate::kernel::ProvMask::all_below(coord_count),
1168 force_run: false,
1169 }
1170 }
1171
1172 #[inline]
1173 fn set_coords(&mut self, coords: &[u64]) {
1174 self.changed_mask.clear();
1175 for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
1176 if self.core.buffer[i] != c {
1177 self.core.buffer[i] = c;
1178 self.changed_mask.set(i);
1179 self.core.dirty_input(i);
1180 }
1181 }
1182 }
1183
1184 fn mark_input_changed(&mut self, slot: usize) {
1187 self.core.dirty_input(slot);
1188 self.force_run = true;
1189 }
1190
1191 #[inline]
1193 pub fn eval(&mut self, coords: &[u64]) {
1194 self.set_coords(coords);
1195 self.force_run = false;
1196 self.core.drive.stale = true;
1197 self.core.eval_all();
1198 }
1199
1200 fn pull_output(&mut self, name: &str) -> crate::ast::Value {
1201 self.core.pull_named(name)
1202 }
1203
1204 #[inline]
1206 pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
1207 self.core.guard_ref_slot(slot);
1208 self.set_coords(coords);
1209 if !self.force_run
1210 && slot < self.slot_provenance.len()
1211 && !self.slot_provenance[slot].intersects(&self.changed_mask)
1212 {
1213 return self.core.buffer[slot];
1214 }
1215 self.force_run = false;
1216 self.core.drive.stale = true;
1217 self.core.eval_all();
1218 self.core.buffer[slot]
1219 }
1220
1221 kernel_accessors!();
1222}
1223
1224use crate::compile::select::{Engine, Provenance};
1227
1228crate::compile::impl_kernel_trait!(CompiledKernelRaw, Engine::Closures(Provenance::Raw));
1229crate::compile::impl_kernel_trait!(CompiledKernelPush, Engine::Closures(Provenance::Push));
1230crate::compile::impl_kernel_trait!(CompiledKernelPull, Engine::Closures(Provenance::Pull));
1231crate::compile::impl_kernel_trait!(
1232 CompiledKernelPushPull,
1233 Engine::Closures(Provenance::PushPull)
1234);
1235
1236#[inline(always)]
1240fn run_step(
1241 step: &CompiledStep,
1242 buffer: &mut [u64],
1243 none: &mut [bool],
1244 gather: &mut [u64],
1245 scatter: &mut [u64],
1246 scratch: &mut [ScratchBuf],
1247) {
1248 let mut any_none = false;
1249 for (i, &s) in step.input_slots.iter().enumerate() {
1250 gather[i] = buffer[s];
1251 any_none |= none[s];
1252 }
1253 if any_none && !step.accepts_none {
1254 for &s in &step.output_slots {
1255 none[s] = true;
1256 }
1257 return;
1258 }
1259 if matches!(step.op, StepOp::Copy) {
1260 for (&i, &o) in step.input_slots.iter().zip(&step.output_slots) {
1261 buffer[o] = buffer[i];
1262 none[o] = false;
1263 }
1264 return;
1265 }
1266 let (n_in, n_out) = (step.input_slots.len(), step.output_slots.len());
1267 match &step.op {
1268 StepOp::Copy => unreachable!(),
1269 StepOp::U64(op) => op(&gather[..n_in], &mut scatter[..n_out]),
1270 StepOp::Slot(op) => op(
1271 &gather[..n_in],
1272 &mut scatter[..n_out],
1273 &mut scratch[step.scratch_range.0..step.scratch_range.1],
1274 ),
1275 }
1276 for (i, &s) in step.output_slots.iter().enumerate() {
1277 buffer[s] = scatter[i];
1278 none[s] = false;
1279 }
1280}
1281
1282#[inline(always)]
1284fn run_step_fast(
1285 step: &CompiledStep,
1286 buffer: &mut [u64],
1287 gather: &mut [u64],
1288 scatter: &mut [u64],
1289 scratch: &mut [ScratchBuf],
1290) {
1291 if matches!(step.op, StepOp::Copy) {
1292 for (&i, &o) in step.input_slots.iter().zip(&step.output_slots) {
1293 buffer[o] = buffer[i];
1294 }
1295 return;
1296 }
1297 for (i, &s) in step.input_slots.iter().enumerate() {
1298 gather[i] = buffer[s];
1299 }
1300 let (n_in, n_out) = (step.input_slots.len(), step.output_slots.len());
1301 match &step.op {
1302 StepOp::Copy => unreachable!(),
1303 StepOp::U64(op) => op(&gather[..n_in], &mut scatter[..n_out]),
1304 StepOp::Slot(op) => op(
1305 &gather[..n_in],
1306 &mut scatter[..n_out],
1307 &mut scratch[step.scratch_range.0..step.scratch_range.1],
1308 ),
1309 }
1310 for (i, &s) in step.output_slots.iter().enumerate() {
1311 buffer[s] = scatter[i];
1312 }
1313}