use std::collections::HashMap;
use crate::ast::SlotShape;
use crate::ast::{PortType, Value};
use crate::kernel::InputDef;
#[derive(Clone)]
pub(crate) struct ExternSlot {
pub name: String,
pub slot: usize,
pub ty: PortType,
pub value: Value,
pub default: Value,
pub cell: Option<crate::kernel::SharedCell>,
pub seen: Option<u64>,
}
#[derive(Clone, Default)]
pub(crate) struct Externs {
slots: Vec<ExternSlot>,
by_name: HashMap<String, usize>,
input_names: Vec<String>,
by_index: Vec<Option<usize>>,
output_names: Vec<String>,
cursors: Vec<crate::iteration::source::SourceSchema>,
intent: std::sync::Arc<std::sync::atomic::AtomicU64>,
next_bit: u8,
changed: Vec<usize>,
}
impl Externs {
pub(crate) fn new(
input_defs: &[InputDef],
coord_count: usize,
input_starts: &[usize],
cursors: &[crate::iteration::source::SourceSchema],
shared: &[&str],
) -> Result<Self, String> {
let mut slots = Vec::new();
let mut by_name = HashMap::new();
let mut by_index = vec![None; input_defs.len()];
for (i, def) in input_defs.iter().enumerate().skip(coord_count) {
if def.port_type.slot_color() == crate::ast::SlotColor::Imm2 {
return Err(format!(
"extern '{}' has type {}, a two-slot immediate; the compiled engines carry \
one-slot carriers and by-reference externs (strings, byte strings, JSON, \
extension values, handles)",
def.name, def.port_type,
));
}
by_name.insert(def.name.clone(), slots.len());
by_index[i] = Some(slots.len());
slots.push(ExternSlot {
name: def.name.clone(),
slot: input_starts[i],
ty: def.port_type,
value: def.default.clone(),
default: def.default.clone(),
cell: None,
seen: None,
});
}
let mut externs = Self {
slots,
by_name,
input_names: input_defs.iter().map(|d| d.name.clone()).collect(),
by_index,
output_names: Vec::new(),
cursors: cursors.to_vec(),
intent: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0)),
next_bit: 0,
changed: Vec::new(),
};
for name in shared {
if let Some(&i) = externs.by_name.get(*name) {
let cell = externs.new_cell(externs.slots[i].value.clone());
externs.slots[i].seen = Some(cell.snapshot().1);
externs.slots[i].cell = Some(cell);
}
}
Ok(externs)
}
fn new_cell(&mut self, initial: Value) -> crate::kernel::SharedCell {
let bit = self.next_bit;
self.next_bit = self.next_bit.saturating_add(1).min(63);
std::sync::Arc::new(crate::kernel::SharedCellInner::new(
initial,
self.intent.clone(),
bit,
))
}
pub(crate) fn reseed_cells(&mut self) {
self.intent = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
self.next_bit = 0;
for i in 0..self.slots.len() {
if self.slots[i].cell.is_none() {
continue;
}
let cell = self.new_cell(self.slots[i].value.clone());
self.slots[i].seen = Some(cell.snapshot().1);
self.slots[i].cell = Some(cell);
}
}
pub(crate) fn attach_cell(
&mut self,
name: &str,
cell: crate::kernel::SharedCell,
) -> Result<usize, String> {
let Some(&i) = self.by_name.get(name) else {
let known: Vec<&str> = self
.slots
.iter()
.filter(|s| s.cell.is_some())
.map(|s| s.name.as_str())
.collect();
return Err(format!(
"no `shared` binding named '{name}'; this kernel's shared bindings are {known:?}"
));
};
let s = &mut self.slots[i];
if s.cell.is_none() {
return Err(format!(
"'{name}' is an extern, not a `shared` binding; only a `shared` binding takes a cell"
));
}
s.cell = Some(cell);
s.seen = None;
Ok(s.slot)
}
pub(crate) fn shared_cells(&self) -> Vec<crate::kernel::SharedCellEntry> {
self.slots
.iter()
.filter_map(|s| {
s.cell.as_ref().map(|cell| crate::kernel::SharedCellEntry {
name: s.name.clone(),
port_type: s.ty,
cell: cell.clone(),
})
})
.collect()
}
pub(crate) fn cells_dirty(&self) -> bool {
self.slots.iter().any(|s| match (&s.cell, s.seen) {
(Some(cell), seen) => {
Some(cell.revision.load(std::sync::atomic::Ordering::Acquire)) != seen
}
(None, _) => false,
})
}
pub(crate) fn refresh_cells(&mut self, buffer: &mut [u64]) {
for s in &mut self.slots {
let Some(cell) = &s.cell else {
continue;
};
if Some(cell.revision.load(std::sync::atomic::Ordering::Acquire)) == s.seen {
continue;
}
let (value, revision) = cell.snapshot();
s.value = value;
s.seen = Some(revision);
write_through(s, buffer);
self.changed.push(s.slot);
}
}
#[inline]
pub(crate) fn has_changed(&self) -> bool {
!self.changed.is_empty()
}
pub(crate) fn take_changed(&mut self) -> Vec<usize> {
std::mem::take(&mut self.changed)
}
pub(crate) fn return_changed(&mut self, mut list: Vec<usize>) {
list.clear();
self.changed = list;
}
pub(crate) fn input_names(&self) -> &[String] {
&self.input_names
}
pub(crate) fn set_output_names(&mut self, names: &[String]) {
self.output_names = names.to_vec();
}
pub(crate) fn output_names(&self) -> &[String] {
&self.output_names
}
#[cfg(feature = "jit")]
pub(crate) fn unset_slots(&self) -> Vec<usize> {
self.slots
.iter()
.filter(|s| s.value == Value::None)
.map(|s| s.slot)
.collect()
}
pub(crate) fn cursor_schemas(&self) -> &[crate::iteration::source::SourceSchema] {
&self.cursors
}
pub(crate) fn set_cursor_extent(&mut self, index: usize, extent: u64) {
if let Some(schema) = self.cursors.get_mut(index) {
schema.extent = Some(extent);
}
}
pub(crate) fn cursor_writes(
&self,
name: &str,
partition: &crate::iteration::cursor_partition::Partition,
) -> Result<Vec<(String, Value)>, String> {
if !self.cursors.iter().any(|c| c.name == name) {
let known: Vec<&str> = self.cursors.iter().map(|c| c.name.as_str()).collect();
return Err(format!(
"no cursor named '{name}'; this program's cursors are {known:?}"
));
}
Ok(
crate::iteration::cursor_partition::cursor_slot_writes(name, partition)
.into_iter()
.filter(|(slot, _)| self.by_name.contains_key(slot))
.collect(),
)
}
pub(crate) fn seed(&self, buffer: &mut [u64], mut none: Option<&mut [bool]>) -> bool {
let mut any_none = false;
for s in &self.slots {
write_through(s, buffer);
let unset = s.value == Value::None;
any_none |= unset;
if let Some(mask) = none.as_deref_mut() {
mask[s.slot] = unset;
}
}
any_none
}
pub(crate) fn any_unset(&self) -> bool {
self.slots.iter().any(|s| s.value == Value::None)
}
#[cfg(feature = "jit")]
pub(crate) fn first_unset(&self) -> Option<(&str, PortType)> {
self.slots
.iter()
.find(|s| s.value == Value::None)
.map(|s| (s.name.as_str(), s.ty))
}
pub(crate) fn set(
&mut self,
name: &str,
value: Value,
buffer: &mut [u64],
) -> Result<(usize, bool), String> {
let Some(&i) = self.by_name.get(name) else {
if self.input_names.iter().any(|n| n == name) {
return Err(format!("'{name}' is a coordinate; set it with set_inputs"));
}
let known: Vec<&str> = self.slots.iter().map(|s| s.name.as_str()).collect();
return Err(format!(
"no extern named '{name}'; this kernel's externs are {known:?}"
));
};
self.set_slot(i, value, buffer)
}
pub(crate) fn set_at(
&mut self,
index: usize,
value: Value,
buffer: &mut [u64],
) -> Result<(usize, bool), String> {
match self.by_index.get(index) {
Some(Some(i)) => self.set_slot(*i, value, buffer),
Some(None) => Err(format!(
"'{}' is a coordinate; set it with set_inputs",
self.input_names[index]
)),
None => Err(format!(
"no input at index {index}; this program's inputs are {:?}",
self.input_names
)),
}
}
fn set_slot(
&mut self,
i: usize,
value: Value,
buffer: &mut [u64],
) -> Result<(usize, bool), String> {
let s = &mut self.slots[i];
if !value.satisfies_slot(s.ty) {
return Err(format!(
"input '{}' is declared {} but was set to a {} value",
s.name,
s.ty,
value.port_type()
));
}
s.value = value;
if let Some(cell) = &s.cell {
cell.publish(s.value.clone());
s.seen = Some(cell.revision.load(std::sync::atomic::Ordering::Acquire));
}
write_through(s, buffer);
Ok((s.slot, s.value == Value::None))
}
pub(crate) fn reset_to_program(&mut self, buffer: &mut [u64]) {
for s in &mut self.slots {
s.value = s.default.clone();
s.seen = None;
write_through(s, buffer);
}
self.reseed_cells();
}
pub(crate) fn value(&self, name: &str) -> Option<Value> {
self.by_name.get(name).map(|&i| self.slots[i].value.clone())
}
pub(crate) fn names(&self) -> Vec<(&str, PortType)> {
self.slots.iter().map(|s| (s.name.as_str(), s.ty)).collect()
}
}
fn write_through(s: &ExternSlot, buffer: &mut [u64]) {
match s.ty.slot_color() {
crate::ast::SlotColor::Ref2 => {
let (p, l) = match &s.value {
Value::None => crate::compile::marshal::empty_pair(),
v => crate::compile::marshal::borrow_pair(v).unwrap_or_else(|| {
panic!(
"extern '{}' ({}) holds a {} value, which has no slot form",
s.name,
s.ty,
v.port_type()
)
}),
};
buffer[s.slot] = p;
buffer[s.slot + 1] = l;
}
_ => buffer[s.slot] = carrier_bits(&s.value),
}
}
fn carrier_bits(v: &Value) -> u64 {
match v {
Value::U64(n) => *n,
Value::I64(n) => *n as u64,
Value::F64(f) => f.to_bits(),
Value::Bool(b) => u64::from(*b),
_ => 0,
}
}