use std::collections::HashMap;
use crate::ast::SlotShape;
use crate::ast::{PortType, Value};
use crate::kernel::InputDef;
use crate::kernel::WriteError;
#[derive(Clone)]
pub(crate) struct ExternSlot {
pub name: String,
pub slot: usize,
pub ty: PortType,
pub reported: PortType,
pub value: Value,
pub default: Value,
pub cell: Option<crate::kernel::SharedCell>,
pub seen: Option<u64>,
pub is_const: bool,
pub init_only: bool,
pub register_start: bool,
}
impl ExternSlot {
#[inline]
fn counted_unset(&self) -> bool {
!self.init_only && self.value == Value::None
}
#[inline]
fn seeded(&mut self) {
if self.register_start {
self.init_only = false;
}
}
}
fn init_only(name: &str, is_const: bool, inits: &[crate::kernel::ConstInit]) -> bool {
is_const
|| inits
.iter()
.any(|c| c.slot == name || c.fallback.as_deref() == Some(name))
}
fn register_start(name: &str, inits: &[crate::kernel::ConstInit]) -> bool {
inits.iter().any(|c| c.register && c.slot == name)
}
#[cfg(feature = "jit")]
pub(crate) fn unset_read_inputs(
input_defs: &[InputDef],
coord_count: usize,
inits: &[crate::kernel::ConstInit],
) -> Vec<bool> {
input_defs
.iter()
.enumerate()
.map(|(i, def)| {
let is_const = def.kind == crate::kernel::InputKind::Const;
i >= coord_count
&& (!init_only(&def.name, is_const, inits) || register_start(&def.name, inits))
})
.collect()
}
#[derive(Default)]
struct ScopeCells {
output_modifiers: HashMap<String, crate::dsl::ast::BindingModifier>,
transit_cells: Vec<crate::kernel::SharedCellEntry>,
output_cells: std::sync::Mutex<Vec<Option<crate::kernel::SharedCell>>>,
write_throughs: Vec<(String, String)>,
origins: Vec<crate::kernel::TypeOrigin>,
const_inits: Vec<crate::kernel::ConstInit>,
fixed_outputs: std::collections::HashSet<String>,
inherited_outputs: Vec<String>,
}
#[derive(Default)]
pub(crate) struct Externs {
slots: Vec<ExternSlot>,
by_name: HashMap<String, usize>,
input_names: Vec<String>,
coordinates: u32,
coordinate_slots: u32,
unset_read: u32,
by_index: Vec<Option<usize>>,
output_names: Vec<String>,
cursors: Vec<crate::iteration::source::SourceSchema>,
scope: Box<ScopeCells>,
intent: std::sync::Arc<std::sync::atomic::AtomicU64>,
next_bit: std::sync::atomic::AtomicU8,
broadcasting: std::sync::atomic::AtomicBool,
changed: Vec<usize>,
ledger: std::sync::Arc<crate::kernel::CompileLedger>,
resources: crate::resource::ResourceScope,
graph_identity: [u8; 32],
}
impl Clone for Externs {
fn clone(&self) -> Self {
Self {
slots: self.slots.clone(),
by_name: self.by_name.clone(),
input_names: self.input_names.clone(),
coordinates: self.coordinates,
coordinate_slots: self.coordinate_slots,
unset_read: self.unset_read,
by_index: self.by_index.clone(),
output_names: self.output_names.clone(),
cursors: self.cursors.clone(),
scope: Box::new(ScopeCells {
output_modifiers: self.scope.output_modifiers.clone(),
transit_cells: self.scope.transit_cells.clone(),
output_cells: std::sync::Mutex::new(Vec::new()),
write_throughs: self.scope.write_throughs.clone(),
origins: self.scope.origins.clone(),
const_inits: self.scope.const_inits.clone(),
fixed_outputs: self.scope.fixed_outputs.clone(),
inherited_outputs: self.scope.inherited_outputs.clone(),
}),
intent: self.intent.clone(),
next_bit: std::sync::atomic::AtomicU8::new(
self.next_bit.load(std::sync::atomic::Ordering::Relaxed),
),
broadcasting: std::sync::atomic::AtomicBool::new(false),
changed: self.changed.clone(),
ledger: self.ledger.clone(),
resources: self.resources.clone(),
graph_identity: self.graph_identity,
}
}
}
impl Externs {
pub(crate) fn new(
input_defs: &[InputDef],
coord_count: usize,
input_starts: &[usize],
cursors: &[crate::iteration::source::SourceSchema],
shared: &[&str],
ledger: std::sync::Arc<crate::kernel::CompileLedger>,
) -> 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,
reported: def.converts_to.unwrap_or(def.port_type),
value: def.default.clone(),
default: def.default.clone(),
cell: None,
seen: None,
is_const: def.kind == crate::kernel::InputKind::Const,
init_only: def.kind == crate::kernel::InputKind::Const,
register_start: false,
});
}
let mut externs = Self {
slots,
by_name,
scope: Box::default(),
input_names: input_defs.iter().map(|d| d.name.clone()).collect(),
coordinates: coord_count as u32,
coordinate_slots: input_defs
.iter()
.take(coord_count)
.map(|d| crate::ast::SlotShape::slot_width(&d.port_type))
.sum::<usize>() as u32,
unset_read: 0,
by_index,
output_names: Vec::new(),
cursors: cursors.to_vec(),
intent: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0)),
next_bit: std::sync::atomic::AtomicU8::new(0),
broadcasting: std::sync::atomic::AtomicBool::new(false),
changed: Vec::new(),
ledger,
resources: crate::resource::ResourceScope::new(),
graph_identity: [0; 32],
};
externs.scope.origins = input_defs.iter().map(|d| d.type_origin).collect();
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);
}
}
externs.recount_unset();
externs.ledger.record();
Ok(externs)
}
#[cfg(feature = "jit")]
pub(crate) fn coordinates_only(coordinates: usize) -> Self {
Self {
coordinates: coordinates as u32,
coordinate_slots: coordinates as u32,
..Self::default()
}
}
#[inline]
pub(crate) fn coordinate_slots(&self) -> usize {
self.coordinate_slots as usize
}
pub(crate) fn ledger(&self) -> &std::sync::Arc<crate::kernel::CompileLedger> {
&self.ledger
}
pub(crate) fn resources(&self) -> &crate::resource::ResourceScope {
&self.resources
}
pub(crate) fn set_resources(&mut self, resources: crate::resource::ResourceScope) {
self.resources = resources;
}
pub(crate) fn graph_identity(&self) -> [u8; 32] {
self.graph_identity
}
pub(crate) fn set_graph_identity(&mut self, digest: [u8; 32]) {
self.graph_identity = digest;
}
pub(crate) fn inherited_outputs(&self) -> &[String] {
&self.scope.inherited_outputs
}
pub(crate) fn set_inherited_outputs(&mut self, mut names: Vec<String>) {
names.sort();
names.dedup();
self.scope.inherited_outputs = names;
}
pub(crate) fn output_cell(&self, slot: usize, initial: Value) -> crate::kernel::SharedCell {
let bit = {
let cells = self
.scope
.output_cells
.lock()
.expect("output cells poisoned");
if let Some(Some(cell)) = cells.get(slot) {
return cell.clone();
}
self.next_cell_bit()
};
let cell = std::sync::Arc::new(crate::kernel::SharedCellInner::new(
initial,
self.intent.clone(),
bit,
));
let mut cells = self
.scope
.output_cells
.lock()
.expect("output cells poisoned");
if cells.len() <= slot {
cells.resize(slot + 1, None);
}
let cell = cells[slot].get_or_insert(cell).clone();
self.broadcasting
.store(true, std::sync::atomic::Ordering::Release);
cell
}
#[inline]
pub(crate) fn published_output(&self, slot: usize) -> Option<crate::kernel::SharedCell> {
let cells = self
.scope
.output_cells
.lock()
.expect("output cells poisoned");
cells.get(slot)?.clone()
}
#[inline]
pub(crate) fn broadcasts(&self) -> bool {
self.broadcasting.load(std::sync::atomic::Ordering::Acquire)
}
fn next_cell_bit(&self) -> u8 {
use std::sync::atomic::Ordering;
let mut bit = self.next_bit.load(Ordering::Relaxed);
loop {
let next = bit.saturating_add(1).min(63);
match self.next_bit.compare_exchange_weak(
bit,
next,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => return bit.min(63),
Err(seen) => bit = seen,
}
}
}
fn new_cell(&self, initial: Value) -> crate::kernel::SharedCell {
std::sync::Arc::new(crate::kernel::SharedCellInner::new(
initial,
self.intent.clone(),
self.next_cell_bit(),
))
}
pub(crate) fn reseed_cells(&mut self) {
self.intent = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
self.next_bit.store(0, std::sync::atomic::Ordering::Relaxed);
self.scope
.output_cells
.lock()
.expect("output cells poisoned")
.clear();
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 bind_cell(
&mut self,
name: &str,
cell: crate::kernel::SharedCell,
) -> Option<usize> {
let &i = self.by_name.get(name)?;
let s = &mut self.slots[i];
s.cell = Some(cell);
s.seen = None;
Some(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();
let was_counted = s.counted_unset();
s.value = value;
s.seen = Some(revision);
if revision > 0 {
s.seeded();
}
self.unset_read = track_unset(self.unset_read, was_counted, s);
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 set_output_modifiers(
&mut self,
modifiers: &HashMap<String, crate::dsl::ast::BindingModifier>,
) {
self.scope.output_modifiers = modifiers.clone();
}
pub(crate) fn set_const_inits(&mut self, inits: &[crate::kernel::ConstInit]) {
self.scope.const_inits = inits.to_vec();
for s in &mut self.slots {
s.register_start = register_start(&s.name, inits);
s.init_only = init_only(&s.name, s.is_const, inits);
}
self.recount_unset();
}
fn recount_unset(&mut self) {
self.unset_read = self.slots.iter().filter(|s| s.counted_unset()).count() as u32;
}
pub(crate) fn const_inits(&self) -> &[crate::kernel::ConstInit] {
&self.scope.const_inits
}
pub(crate) fn set_fixed_outputs(&mut self, names: std::collections::HashSet<String>) {
self.scope.fixed_outputs = names;
}
pub(crate) fn is_fixed_output(&self, name: &str) -> bool {
self.scope.fixed_outputs.contains(name)
}
pub(crate) fn is_const_index(&self, index: usize) -> bool {
matches!(self.by_index.get(index), Some(Some(i)) if self.slots[*i].is_const)
}
pub(crate) fn is_const_name(&self, name: &str) -> bool {
self.by_name
.get(name)
.is_some_and(|&i| self.slots[i].is_const)
}
pub(crate) fn input_port_type(&self, name: &str) -> Option<PortType> {
if let Some(&i) = self.by_name.get(name) {
return Some(self.slots[i].reported);
}
self.input_names
.iter()
.any(|n| n == name)
.then_some(PortType::U64)
}
pub(crate) fn input_type_origin(&self, name: &str) -> Option<crate::kernel::TypeOrigin> {
let i = self.input_names.iter().position(|n| n == name)?;
self.scope.origins.get(i).copied()
}
pub(crate) fn output_modifier(&self, name: &str) -> crate::dsl::ast::BindingModifier {
self.scope
.output_modifiers
.get(name)
.copied()
.unwrap_or(crate::dsl::ast::BindingModifier::NONE)
}
pub(crate) fn set_transit_cells(&mut self, cells: Vec<crate::kernel::SharedCellEntry>) {
self.scope.transit_cells = cells;
}
pub(crate) fn cells_in_scope(&self) -> Vec<crate::kernel::SharedCellEntry> {
let mut by_name: HashMap<String, crate::kernel::SharedCellEntry> = self
.scope
.transit_cells
.iter()
.map(|e| (e.name.clone(), e.clone()))
.collect();
for entry in self.shared_cells() {
by_name.insert(entry.name.clone(), entry);
}
by_name.into_values().collect()
}
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()
}
#[cfg(feature = "jit")]
pub(crate) fn unset_read_slots(&self) -> Vec<usize> {
let mut slots: Vec<usize> = self
.slots
.iter()
.filter(|s| !s.init_only || s.register_start)
.flat_map(|s| {
let pair = s.ty.slot_color() == crate::ast::SlotColor::Ref2;
std::iter::once(s.slot).chain(pair.then_some(s.slot + 1))
})
.collect();
slots.sort_unstable();
slots
}
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)>, WriteError> {
if !self.cursors.iter().any(|c| c.name == name) {
return Err(WriteError::UnknownWire {
key: name.to_string(),
known: self.cursors.iter().map(|c| c.name.clone()).collect(),
});
}
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 slot_is_unset(&self, slot: usize) -> bool {
self.slots
.iter()
.any(|s| s.slot == slot && s.value == Value::None)
}
pub(crate) fn any_unset(&self) -> bool {
self.slots.iter().any(|s| s.value == Value::None)
}
#[cfg(feature = "jit")]
#[inline]
pub(crate) fn any_unset_read(&self) -> bool {
self.unset_read != 0
}
#[cfg(feature = "jit")]
pub(crate) fn first_unset(&self, reads: impl Fn(usize) -> bool) -> Option<(&str, PortType)> {
self.slots
.iter()
.find(|s| {
s.counted_unset()
&& (reads(s.slot)
|| (s.ty.slot_color() == crate::ast::SlotColor::Ref2 && reads(s.slot + 1)))
})
.map(|s| (s.name.as_str(), s.ty))
}
pub(crate) fn set(
&mut self,
name: &str,
value: Value,
buffer: &mut [u64],
) -> Result<(usize, bool), WriteError> {
let Some(&i) = self.by_name.get(name) else {
if self.input_names.iter().any(|n| n == name) {
return Err(WriteError::CoordinateSlot {
slot: name.to_string(),
});
}
return Err(WriteError::UnknownWire {
key: name.to_string(),
known: self.input_names.clone(),
});
};
self.set_slot(i, value, buffer)
}
pub(crate) fn set_at(
&mut self,
index: usize,
value: Value,
buffer: &mut [u64],
) -> Result<(usize, bool), WriteError> {
match self.by_index.get(index) {
Some(Some(i)) => self.set_slot(*i, value, buffer),
Some(None) => Err(WriteError::CoordinateSlot {
slot: self.input_names[index].clone(),
}),
None => Err(WriteError::UnknownWire {
key: format!("wire[{index}]"),
known: self.input_names.clone(),
}),
}
}
fn set_slot(
&mut self,
i: usize,
value: Value,
buffer: &mut [u64],
) -> Result<(usize, bool), WriteError> {
let s = &mut self.slots[i];
if !value.satisfies_slot(s.ty) {
return Err(WriteError::TypeMismatch {
slot: s.name.clone(),
expected: s.ty,
got: value.port_type(),
});
}
let was_counted = s.counted_unset();
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));
}
s.seeded();
write_through(s, buffer);
let s = &self.slots[i];
self.unset_read = track_unset(self.unset_read, was_counted, s);
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;
if s.register_start {
s.init_only = true;
}
write_through(s, buffer);
}
self.recount_unset();
self.reseed_cells();
}
pub(crate) fn value(&self, name: &str) -> Option<Value> {
self.by_name.get(name).map(|&i| self.slots[i].value.clone())
}
#[inline]
pub(crate) fn coordinate_count(&self) -> usize {
self.coordinates as usize
}
fn slot_at(&self, index: usize) -> Option<&ExternSlot> {
self.by_index
.get(index)
.copied()
.flatten()
.map(|i| &self.slots[i])
}
pub(crate) fn value_at(&self, index: usize) -> Option<Value> {
self.slot_at(index).map(|s| match &s.cell {
Some(cell) => cell.snapshot().0,
None => s.value.clone(),
})
}
pub(crate) fn default_at(&self, index: usize) -> Option<Value> {
self.slot_at(index).map(|s| s.default.clone())
}
pub(crate) fn is_cell_bound_at(&self, index: usize) -> bool {
self.slot_at(index).is_some_and(|s| s.cell.is_some())
}
pub(crate) fn write_throughs(&self) -> &[(String, String)] {
&self.scope.write_throughs
}
pub(crate) fn set_write_throughs(&mut self, pairs: Vec<(String, String)>) {
self.scope.write_throughs = pairs;
}
pub(crate) fn names(&self) -> Vec<(&str, PortType)> {
self.slots
.iter()
.map(|s| (s.name.as_str(), s.reported))
.collect()
}
}
#[inline]
fn track_unset(count: u32, was_counted: bool, s: &ExternSlot) -> u32 {
match (was_counted, s.counted_unset()) {
(false, true) => count + 1,
(true, false) => count - 1,
_ => count,
}
}
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 if s.ty == PortType::Dyn => (v as *const Value as usize as u64, 1),
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),
Value::None => 0,
other => panic!(
"an extern slot holds a {:?}, which is not a one-slot carrier",
other.port_type()
),
}
}