use std::collections::HashMap;
use crate::ast::{CompiledSlotOp, CompiledU64Op, ScratchBuf, ScratchElem};
pub(crate) enum StepOp {
U64(CompiledU64Op),
Slot(CompiledSlotOp),
}
pub(crate) struct P2Step {
pub(crate) op: StepOp,
pub(crate) input_slots: Vec<usize>,
pub(crate) output_slots: Vec<usize>,
pub(crate) scratch: Vec<ScratchElem>,
pub(crate) ref_output_starts: Vec<usize>,
}
struct CompiledStep {
op: StepOp,
input_slots: Vec<usize>,
output_slots: Vec<usize>,
scratch_range: (usize, usize),
}
struct KernelCore {
buffer: Vec<u64>,
coord_count: usize,
steps: Vec<CompiledStep>,
output_map: HashMap<String, usize>,
gather_buf: Vec<u64>,
scatter_buf: Vec<u64>,
scratch: Vec<ScratchBuf>,
ref_slots: Vec<bool>,
ref_scratch: Vec<(usize, usize)>,
}
impl KernelCore {
#[cfg(debug_assertions)]
fn validate_refs(&self) {
for &(slot, idx) in &self.ref_scratch {
let (p, l) = self.scratch[idx].ptr_len();
assert!(
self.buffer[slot] == p && self.buffer[slot + 1] == l,
"S9 ref-validator: slot pair ({slot}, {}) = ({:#x}, {}) \
does not match scratch[{idx}] = ({p:#x}, {l}) — a slot \
op failed to republish or wrote the wrong slots",
slot + 1,
self.buffer[slot],
self.buffer[slot + 1],
);
}
}
#[inline]
fn guard_ref_slot(&self, slot: usize) {
if self.ref_slots.get(slot).copied().unwrap_or(false) {
panic!(
"S2 pointer containment: slot {slot} is Ref2-colored; raw \
u64 readers would leak an interior address. Use the typed \
borrow-checked accessor (read_vec_*) or copy out."
);
}
}
fn ref_entry(&self, slot: usize) -> &ScratchBuf {
match self.ref_scratch.iter().find(|(s, _)| *s == slot) {
Some(&(_, idx)) => &self.scratch[idx],
None if self.ref_slots.get(slot).copied().unwrap_or(false) => panic!(
"slot {slot} is a Ref pair owned by the CALLER (a kernel \
input) — read it on the caller side"
),
None => panic!("slot {slot} is not a Ref2-colored slot"),
}
}
}
fn build_core(
coord_count: usize,
total_slots: usize,
steps: Vec<P2Step>,
output_map: HashMap<String, usize>,
ref_slots: Vec<bool>,
) -> KernelCore {
let max_inputs = steps.iter().map(|s| s.input_slots.len()).max().unwrap_or(0);
let max_outputs = steps.iter().map(|s| s.output_slots.len()).max().unwrap_or(0);
let mut scratch: Vec<ScratchBuf> = Vec::new();
let mut ref_scratch: Vec<(usize, usize)> = Vec::new();
let compiled_steps: Vec<CompiledStep> = steps.into_iter()
.map(|step| {
let start = scratch.len();
scratch.extend(step.scratch.iter().map(|e| ScratchBuf::new(*e)));
assert_eq!(
step.ref_output_starts.len(),
step.scratch.len(),
"slot-op step declares {} scratch entries for {} Ref \
output ports",
step.scratch.len(),
step.ref_output_starts.len(),
);
for (k, &slot) in step.ref_output_starts.iter().enumerate() {
ref_scratch.push((slot, start + k));
}
CompiledStep {
op: step.op,
input_slots: step.input_slots,
output_slots: step.output_slots,
scratch_range: (start, scratch.len()),
}
})
.collect();
KernelCore {
buffer: vec![0u64; total_slots],
coord_count,
steps: compiled_steps,
output_map,
gather_buf: vec![0u64; max_inputs],
scatter_buf: vec![0u64; max_outputs],
scratch,
ref_slots,
ref_scratch,
}
}
fn compute_slot_provenance(
coord_count: usize,
total_slots: usize,
input_dependents: &[Vec<usize>],
steps: &[CompiledStep],
) -> Vec<crate::kernel::ProvMask> {
let step_count = steps.len();
let mut step_prov: Vec<crate::kernel::ProvMask> =
(0..step_count).map(|_| crate::kernel::ProvMask::empty()).collect();
for (input_idx, deps) in input_dependents.iter().enumerate() {
for &step_idx in deps {
if step_idx < step_count {
step_prov[step_idx].set(input_idx);
}
}
}
let mut slot_provenance: Vec<crate::kernel::ProvMask> =
(0..total_slots).map(|_| crate::kernel::ProvMask::empty()).collect();
for (i, slot) in slot_provenance.iter_mut().enumerate().take(coord_count) {
slot.set(i);
}
for (step_idx, step) in steps.iter().enumerate() {
for &slot in &step.output_slots {
if slot < slot_provenance.len() {
slot_provenance[slot] = step_prov[step_idx].clone();
}
}
}
slot_provenance
}
macro_rules! kernel_accessors {
() => {
pub fn coord_count(&self) -> usize { self.core.coord_count }
pub fn resolve_output(&self, name: &str) -> Option<usize> {
self.core.output_map.get(name).copied()
}
pub fn output_names(&self) -> Vec<&str> {
self.core.output_map.keys().map(|s| s.as_str()).collect()
}
#[inline]
pub fn get_slot(&self, slot: usize) -> u64 {
self.core.guard_ref_slot(slot);
self.core.buffer[slot]
}
#[inline]
pub fn get(&self, name: &str) -> u64 {
let slot = self.core.output_map[name];
self.core.guard_ref_slot(slot);
self.core.buffer[slot]
}
crate::compile::ref_readers!();
};
}
#[inline]
fn eval_all_steps(core: &mut KernelCore) {
for step in &core.steps {
for (i, &s) in step.input_slots.iter().enumerate() {
core.gather_buf[i] = core.buffer[s];
}
match &step.op {
StepOp::U64(op) => op(
&core.gather_buf[..step.input_slots.len()],
&mut core.scatter_buf[..step.output_slots.len()],
),
StepOp::Slot(op) => op(
&core.gather_buf[..step.input_slots.len()],
&mut core.scatter_buf[..step.output_slots.len()],
&mut core.scratch[step.scratch_range.0..step.scratch_range.1],
),
}
for (i, &s) in step.output_slots.iter().enumerate() {
core.buffer[s] = core.scatter_buf[i];
}
}
#[cfg(debug_assertions)]
core.validate_refs();
}
pub struct CompiledKernelRaw {
core: KernelCore,
}
impl CompiledKernelRaw {
pub(crate) fn new(
coord_count: usize,
total_slots: usize,
steps: Vec<P2Step>,
output_map: HashMap<String, usize>,
ref_slots: Vec<bool>,
) -> Self {
Self { core: build_core(coord_count, total_slots, steps, output_map, ref_slots) }
}
#[inline]
pub fn eval(&mut self, coords: &[u64]) {
self.core.buffer[..self.core.coord_count.min(coords.len())]
.copy_from_slice(&coords[..self.core.coord_count.min(coords.len())]);
eval_all_steps(&mut self.core);
}
#[inline]
pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
self.core.guard_ref_slot(slot);
self.eval(coords);
self.core.buffer[slot]
}
kernel_accessors!();
}
pub struct CompiledKernelPush {
core: KernelCore,
node_clean: Vec<bool>,
input_dependents: Vec<Vec<usize>>,
}
impl CompiledKernelPush {
pub(crate) fn new(
coord_count: usize,
total_slots: usize,
steps: Vec<P2Step>,
output_map: HashMap<String, usize>,
input_dependents: Vec<Vec<usize>>,
ref_slots: Vec<bool>,
) -> Self {
let step_count = steps.len();
Self {
core: build_core(coord_count, total_slots, steps, output_map, ref_slots),
node_clean: vec![false; step_count],
input_dependents,
}
}
#[inline]
fn set_inputs(&mut self, coords: &[u64]) {
for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
if self.core.buffer[i] != c {
self.core.buffer[i] = c;
if i < self.input_dependents.len() {
for &step_idx in &self.input_dependents[i] {
self.node_clean[step_idx] = false;
}
}
}
}
}
#[inline]
pub fn eval(&mut self, coords: &[u64]) {
self.set_inputs(coords);
for (step_idx, step) in self.core.steps.iter().enumerate() {
if self.node_clean[step_idx] { continue; }
for (i, &s) in step.input_slots.iter().enumerate() {
self.core.gather_buf[i] = self.core.buffer[s];
}
match &step.op {
StepOp::U64(op) => op(
&self.core.gather_buf[..step.input_slots.len()],
&mut self.core.scatter_buf[..step.output_slots.len()],
),
StepOp::Slot(op) => op(
&self.core.gather_buf[..step.input_slots.len()],
&mut self.core.scatter_buf[..step.output_slots.len()],
&mut self.core.scratch[step.scratch_range.0..step.scratch_range.1],
),
}
for (i, &s) in step.output_slots.iter().enumerate() {
self.core.buffer[s] = self.core.scatter_buf[i];
}
self.node_clean[step_idx] = true;
}
#[cfg(debug_assertions)]
self.core.validate_refs();
}
#[inline]
pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
self.core.guard_ref_slot(slot);
self.eval(coords);
self.core.buffer[slot]
}
kernel_accessors!();
}
pub struct CompiledKernelPull {
core: KernelCore,
slot_provenance: Vec<crate::kernel::ProvMask>,
changed_mask: crate::kernel::ProvMask,
}
impl CompiledKernelPull {
pub(crate) fn new(
coord_count: usize,
total_slots: usize,
steps: Vec<P2Step>,
output_map: HashMap<String, usize>,
input_dependents: &[Vec<usize>],
ref_slots: Vec<bool>,
) -> Self {
let core = build_core(coord_count, total_slots, steps, output_map, ref_slots);
let slot_provenance = compute_slot_provenance(
coord_count, total_slots, input_dependents, &core.steps);
Self {
core,
slot_provenance,
changed_mask: crate::kernel::ProvMask::all_below(coord_count), }
}
#[inline]
fn set_inputs(&mut self, coords: &[u64]) {
self.changed_mask.clear();
for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
if self.core.buffer[i] != c {
self.core.buffer[i] = c;
self.changed_mask.set(i);
}
}
}
#[inline]
pub fn eval(&mut self, coords: &[u64]) {
self.set_inputs(coords);
eval_all_steps(&mut self.core);
}
#[inline]
pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
self.core.guard_ref_slot(slot);
self.set_inputs(coords);
if slot < self.slot_provenance.len()
&& !self.slot_provenance[slot].intersects(&self.changed_mask) {
return self.core.buffer[slot];
}
eval_all_steps(&mut self.core);
self.core.buffer[slot]
}
kernel_accessors!();
}
pub struct CompiledKernelPushPull {
core: KernelCore,
node_clean: Vec<bool>,
input_dependents: Vec<Vec<usize>>,
slot_provenance: Vec<crate::kernel::ProvMask>,
changed_mask: crate::kernel::ProvMask,
}
impl CompiledKernelPushPull {
pub(crate) fn new(
coord_count: usize,
total_slots: usize,
steps: Vec<P2Step>,
output_map: HashMap<String, usize>,
input_dependents: Vec<Vec<usize>>,
ref_slots: Vec<bool>,
) -> Self {
let step_count = steps.len();
let core = build_core(coord_count, total_slots, steps, output_map, ref_slots);
let slot_provenance = compute_slot_provenance(
coord_count, total_slots, &input_dependents, &core.steps);
Self {
core,
node_clean: vec![false; step_count],
input_dependents,
slot_provenance,
changed_mask: crate::kernel::ProvMask::all_below(coord_count),
}
}
#[inline]
fn set_inputs(&mut self, coords: &[u64]) {
self.changed_mask.clear();
for (i, &c) in coords.iter().enumerate().take(self.core.coord_count) {
if self.core.buffer[i] != c {
self.core.buffer[i] = c;
self.changed_mask.set(i);
if i < self.input_dependents.len() {
for &step_idx in &self.input_dependents[i] {
self.node_clean[step_idx] = false;
}
}
}
}
}
#[inline]
pub fn eval(&mut self, coords: &[u64]) {
self.set_inputs(coords);
for (step_idx, step) in self.core.steps.iter().enumerate() {
if self.node_clean[step_idx] { continue; }
for (i, &s) in step.input_slots.iter().enumerate() {
self.core.gather_buf[i] = self.core.buffer[s];
}
match &step.op {
StepOp::U64(op) => op(
&self.core.gather_buf[..step.input_slots.len()],
&mut self.core.scatter_buf[..step.output_slots.len()],
),
StepOp::Slot(op) => op(
&self.core.gather_buf[..step.input_slots.len()],
&mut self.core.scatter_buf[..step.output_slots.len()],
&mut self.core.scratch[step.scratch_range.0..step.scratch_range.1],
),
}
for (i, &s) in step.output_slots.iter().enumerate() {
self.core.buffer[s] = self.core.scatter_buf[i];
}
self.node_clean[step_idx] = true;
}
#[cfg(debug_assertions)]
self.core.validate_refs();
}
#[inline]
pub fn eval_for_slot(&mut self, coords: &[u64], slot: usize) -> u64 {
self.core.guard_ref_slot(slot);
self.set_inputs(coords);
if slot < self.slot_provenance.len()
&& !self.slot_provenance[slot].intersects(&self.changed_mask) {
return self.core.buffer[slot];
}
for (step_idx, step) in self.core.steps.iter().enumerate() {
if self.node_clean[step_idx] { continue; }
for (i, &s) in step.input_slots.iter().enumerate() {
self.core.gather_buf[i] = self.core.buffer[s];
}
match &step.op {
StepOp::U64(op) => op(
&self.core.gather_buf[..step.input_slots.len()],
&mut self.core.scatter_buf[..step.output_slots.len()],
),
StepOp::Slot(op) => op(
&self.core.gather_buf[..step.input_slots.len()],
&mut self.core.scatter_buf[..step.output_slots.len()],
&mut self.core.scratch[step.scratch_range.0..step.scratch_range.1],
),
}
for (i, &s) in step.output_slots.iter().enumerate() {
self.core.buffer[s] = self.core.scatter_buf[i];
}
self.node_clean[step_idx] = true;
}
#[cfg(debug_assertions)]
self.core.validate_refs();
self.core.buffer[slot]
}
kernel_accessors!();
}