use std::cell::UnsafeCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use super::arena::{Arena, BuildMode};
use super::driver::TriregCharge;
use super::region_check::ForceObserved;
use super::write::{SlotLocation, VcState, WorkerSlot};
use super::{SlotClass, StateCellId, StateLayout};
use crate::netlist::{ClassTemplateId, InstanceId, InstantiatedSignalId, SymbolKey, TemplateKey};
static NEXT_ENGINE_ID: AtomicU64 = AtomicU64::new(0);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct InstanceBase {
pub inline_base: u32,
pub wide_base: u32,
}
pub struct InstanceHandle {
pub base: InstanceBase,
pub template: TemplateKey,
layout: *const StateLayout,
arena: *const Arena,
#[allow(dead_code)]
arena_owner: Rc<Arena>,
#[allow(dead_code)]
templates_owner: Rc<TemplateRegistry>,
vc_owner: Rc<VcState>,
cells: std::cell::UnsafeCell<Vec<StateCellId>>,
foreign: std::cell::RefCell<Vec<(*const InstanceHandle, usize)>>,
windows: std::cell::RefCell<Vec<Option<super::AliasWindow>>>,
wide_scratch: std::cell::RefCell<ScratchRing>,
inline_ptrs: std::cell::UnsafeCell<InlinePtrCache>,
}
type InlinePtrCache = ((usize, usize), Vec<*const u128>);
type ScratchRing = (usize, Vec<(Vec<u64>, Vec<u64>)>);
const WIDE_SCRATCH_SLOTS: usize = 32;
impl InstanceHandle {
pub(crate) fn scratch_wide(&self, aval: Vec<u64>, bval: Vec<u64>) -> (*mut u64, *mut u64, u32) {
let (next, ring) = &mut *self.wide_scratch.borrow_mut();
let at = *next % WIDE_SCRATCH_SLOTS;
*next += 1;
if ring.len() < WIDE_SCRATCH_SLOTS {
ring.push((aval, bval));
} else {
ring[at] = (aval, bval);
}
let (a, b) = &ring[at.min(ring.len() - 1)];
(a.as_ptr().cast_mut(), b.as_ptr().cast_mut(), a.len() as u32)
}
pub(crate) fn arena(&self) -> &Arena {
unsafe { &*self.arena }
}
pub(crate) fn layout(&self) -> &StateLayout {
unsafe { &*self.layout }
}
pub(crate) fn vc(&self) -> &VcState {
&self.vc_owner
}
pub(crate) fn cell_for_slot(&self, idx: usize) -> StateCellId {
let cells = unsafe { &*self.cells.get() };
cells[idx]
}
#[inline]
pub(crate) fn resolve_slot(&self, idx: usize) -> (&InstanceHandle, usize) {
if idx < self.own_slot_count() {
(self, idx)
} else {
self.resolve_foreign_slot(idx)
}
}
#[cold]
#[inline(never)]
fn resolve_foreign_slot(&self, idx: usize) -> (&InstanceHandle, usize) {
let own = self.own_slot_count();
let (owner, slot) = self
.foreign
.borrow()
.get(idx - own)
.copied()
.unwrap_or_else(|| panic!("slot {idx} is neither own nor a registered foreign slot"));
(unsafe { &*owner }, slot)
}
pub(crate) fn set_foreign(
&self,
targets: Vec<(*const InstanceHandle, usize)>,
views: Vec<Option<(super::AliasWindow, StateCellId)>>,
) {
debug_assert_eq!(targets.len(), views.len());
let cells = unsafe { &mut *self.cells.get() };
cells.truncate(self.own_slot_count());
for (&(owner, slot), view) in targets.iter().zip(&views) {
match view {
Some((_, cell)) => cells.push(*cell),
None => cells.push(unsafe { &*owner }.cell_for_slot(slot)),
}
}
*self.windows.borrow_mut() = if views.iter().any(Option::is_some) {
views.iter().map(|v| v.map(|(w, _)| w)).collect()
} else {
Vec::new()
};
*self.foreign.borrow_mut() = targets;
unsafe { &mut *self.inline_ptrs.get() }.1.clear();
}
#[cold]
#[inline(never)]
pub(crate) fn foreign_window(&self, idx: usize) -> Option<super::AliasWindow> {
let own = self.own_slot_count();
self.windows.borrow().get(idx - own).copied().flatten()
}
pub(crate) fn inline_ptr_table(&self) -> *const *const u128 {
let inline = self.arena().inline();
let stamp = (inline.as_ptr() as usize, inline.len());
let cache = unsafe { &mut *self.inline_ptrs.get() };
if cache.0 != stamp || cache.1.is_empty() {
let own = self.own_slot_count();
let windows = self.windows.borrow();
let total = own + self.foreign.borrow().len();
cache.1.clear();
cache.1.extend((0..total).map(|idx| {
if idx >= own && windows.get(idx - own).is_some_and(Option::is_some) {
return std::ptr::null();
}
let (owner, oidx) = self.resolve_slot(idx);
let offset = owner.layout().slots[oidx].offset;
if offset.class != SlotClass::Inline {
return std::ptr::null();
}
let word = owner.base.inline_base + offset.word_offset;
owner
.arena()
.inline()
.get(word as usize)
.map_or(std::ptr::null(), std::ptr::from_ref)
}));
cache.0 = stamp;
}
cache.1.as_ptr()
}
pub(crate) fn own_slot_count(&self) -> usize {
self.layout().slots.len()
}
}
#[derive(Default)]
pub struct TemplateRegistry {
templates: UnsafeCell<Vec<(TemplateKey, Box<StateLayout>)>>,
}
impl std::fmt::Debug for TemplateRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = unsafe { (*self.templates.get()).len() };
f.debug_struct("TemplateRegistry")
.field("template_count", &len)
.finish()
}
}
impl TemplateRegistry {
pub fn new() -> Self {
TemplateRegistry {
templates: UnsafeCell::new(Vec::new()),
}
}
pub unsafe fn register(&self, key: TemplateKey, layout: StateLayout) {
unsafe {
(*self.templates.get()).push((key, Box::new(layout)));
}
}
pub fn layout_of(&self, key: &TemplateKey) -> Option<&StateLayout> {
unsafe { &*self.templates.get() }
.iter()
.find(|(k, _)| k == key)
.map(|(_, layout)| layout.as_ref())
}
pub(crate) fn signature_hash(&self) -> u64 {
use std::hash::Hasher;
let templates: &Vec<(TemplateKey, Box<StateLayout>)> = unsafe { &*self.templates.get() };
let mut hasher = std::collections::hash_map::DefaultHasher::new();
for (key, layout) in templates.iter() {
hasher.write_u32(key.definition);
hasher.write_u64(key.params_hash);
hasher.write_u32(layout.inline_words);
hasher.write_u32(layout.wide_words);
hasher.write_usize(layout.slots.len());
for slot in &layout.slots {
hasher.write_u32(slot.key.0);
hasher.write_u8(match slot.offset.class {
SlotClass::Inline => 0,
SlotClass::Wide => 1,
SlotClass::Memory => 2,
SlotClass::ForceWide => 3,
});
hasher.write_u32(slot.offset.word_offset);
hasher.write_u32(slot.offset.bit_width);
hasher.write_u32(slot.offset.elem_word_count);
hasher.write_u32(slot.offset.row_stride_words);
hasher.write_u32(slot.offset.num_rows);
hasher.write_u8(slot.net_info.is_some() as u8);
}
}
hasher.finish()
}
}
pub struct StateEngine {
pub arena: Rc<Arena>,
pub templates: Rc<TemplateRegistry>,
pub(crate) cells: Vec<SlotLocation>,
pub(crate) vc: Rc<VcState>,
pub(crate) dispatch: super::dispatch::Dispatch,
pub(crate) trireg_charge: HashMap<StateCellId, TriregCharge>,
pub(crate) force_observed: Vec<Box<dyn ForceObserved>>,
pub(crate) cell_reset: Vec<super::rollback::CellReset>,
pub(crate) frame: super::frame::FrameArena,
pub(crate) next_instance_id: u32,
pub(crate) hier_cells: HashMap<InstantiatedSignalId, StateCellId>,
pub(crate) event_cells: HashSet<StateCellId>,
}
impl StateEngine {
pub fn new(templates: TemplateRegistry) -> Self {
Self::new_with_mode(templates, BuildMode::FourState)
}
pub fn new_with_mode(templates: TemplateRegistry, mode: BuildMode) -> Self {
let engine_id = NEXT_ENGINE_ID.fetch_add(1, Ordering::Relaxed);
StateEngine {
arena: Rc::new(Arena::new(mode)),
templates: Rc::new(templates),
cells: Vec::new(),
vc: Rc::new(VcState::new(engine_id)),
dispatch: super::dispatch::Dispatch::new(),
trireg_charge: HashMap::new(),
force_observed: Vec::new(),
cell_reset: Vec::new(),
frame: super::frame::FrameArena::new(),
next_instance_id: 0,
hier_cells: HashMap::new(),
event_cells: HashSet::new(),
}
}
pub fn set_current_time(&mut self, at: vj_abi::SimTime) {
self.vc.set_current_time(at);
}
pub fn set_current_region(&mut self, region: vj_abi::Region) {
self.vc.set_current_region(region);
}
pub fn register_worker_thread(&mut self) -> WorkerSlot {
unsafe { self.vc.register_worker_thread() }
}
pub fn slot_of(&self, template: &TemplateKey, symbol: SymbolKey) -> Option<super::SlotOffset> {
self.templates
.layout_of(template)?
.slots
.iter()
.find(|slot| slot.key == symbol)
.map(|slot| slot.offset)
}
pub fn template_size(&self, template: &TemplateKey) -> Option<(u32, u32)> {
self.templates
.layout_of(template)
.map(|layout| (layout.inline_words, layout.wide_words))
}
pub fn allocate_instance(&mut self, template: TemplateKey) -> Option<Box<InstanceHandle>> {
let (inline_words, wide_words) = self.template_size(&template)?;
let (inline_base, wide_base) = unsafe {
(
self.arena.alloc_inline_words(inline_words),
self.arena.alloc_wide_words(wide_words),
)
};
let layout_ptr: *const StateLayout = self.templates.layout_of(&template)?;
let arena_ptr: *const Arena = &*self.arena;
let layout_ref: &StateLayout = unsafe { &*layout_ptr };
let instance_id = InstanceId(self.next_instance_id);
self.next_instance_id = self
.next_instance_id
.checked_add(1)
.expect("more than u32::MAX instances allocated in one StateEngine");
let mut cells = Vec::with_capacity(layout_ref.slots.len());
for slot in &layout_ref.slots {
let word_offset = match slot.offset.class {
SlotClass::Inline => inline_base + slot.offset.word_offset,
SlotClass::Wide | SlotClass::Memory => wide_base + slot.offset.word_offset,
SlotClass::ForceWide => unreachable!(
"allocate_instance: no SlotDescriptor/SlotOffset the layout builder \
produces is ever SlotClass::ForceWide"
),
};
let loc = SlotLocation {
class: slot.offset.class,
word_offset,
bit_width: slot.offset.bit_width,
elem_word_count: slot.offset.elem_word_count,
row_stride_words: slot.offset.row_stride_words,
num_rows: slot.offset.num_rows,
};
let id = self.alloc_cell(loc);
let reset = if slot.net_info.is_some() {
super::rollback::CellReset::Net
} else if let Some(initial) = &slot.initial {
super::rollback::CellReset::Initial(initial.clone())
} else {
super::rollback::CellReset::AllX
};
self.cell_reset[id.0 as usize] = reset;
self.hier_cells.insert(
InstantiatedSignalId {
instance: instance_id,
member: slot.key,
},
id,
);
cells.push(id);
}
Some(Box::new(InstanceHandle {
base: InstanceBase {
inline_base,
wide_base,
},
template,
layout: layout_ptr,
arena: arena_ptr,
arena_owner: Rc::clone(&self.arena),
templates_owner: Rc::clone(&self.templates),
vc_owner: Rc::clone(&self.vc),
cells: std::cell::UnsafeCell::new(cells),
foreign: std::cell::RefCell::new(Vec::new()),
windows: std::cell::RefCell::new(Vec::new()),
wide_scratch: std::cell::RefCell::new((0, Vec::new())),
inline_ptrs: std::cell::UnsafeCell::new(((0, 0), Vec::new())),
}))
}
}
pub fn class_static_instance(class_template: ClassTemplateId) -> (InstanceId, TemplateKey) {
const RESERVED_BASE: u32 = u32::MAX - (2 << 20);
let n = RESERVED_BASE
.checked_sub(class_template.0)
.unwrap_or_else(|| {
panic!(
"class_static_instance: ClassTemplateId({}) exceeds the reserved \
synthetic-instance-id block (RESERVED_BASE = {RESERVED_BASE}) -- \
more class templates with static properties than this \
implementation's id-space convention supports",
class_template.0
)
});
(
InstanceId(n),
TemplateKey {
definition: n,
params_hash: 2,
},
)
}
impl StateEngine {
#[must_use]
pub fn allocate_class_static_singletons(
&mut self,
layouts: Vec<(ClassTemplateId, StateLayout)>,
) -> HashMap<ClassTemplateId, (InstanceId, Box<InstanceHandle>)> {
let mut out = HashMap::with_capacity(layouts.len());
for (class_template, layout) in layouts {
let (instance_id, key) = class_static_instance(class_template);
unsafe {
self.templates.register(key, layout);
}
let handle = self.allocate_instance(key).unwrap_or_else(|| {
unreachable!(
"allocate_class_static_singletons: class_static_instance's own \
TemplateKey was just registered above, so allocate_instance \
cannot report it unregistered"
)
});
out.insert(class_template, (instance_id, handle));
}
out
}
pub(crate) fn cell_for_instantiated_signal(
&self,
sig: InstantiatedSignalId,
) -> Option<StateCellId> {
self.hier_cells.get(&sig).copied()
}
#[cfg(test)]
pub(crate) fn hier_cells_len(&self) -> usize {
self.hier_cells.len()
}
}
#[cfg_attr(feature = "aot-exports", unsafe(export_name = "sv_rt_inline_ptrs"))]
pub extern "C" fn real_sv_rt_inline_ptrs(handle: vj_abi::SimHandle) -> *const *const u128 {
unsafe { InstanceHandle::from_sim_handle(handle) }.inline_ptr_table()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{SlotClass, SlotDescriptor, SlotOffset};
fn key(definition: u32, params_hash: u64) -> TemplateKey {
TemplateKey {
definition,
params_hash,
}
}
fn inline_slot(sym: u32, word_offset: u32, bit_width: u32) -> SlotDescriptor {
SlotDescriptor {
key: SymbolKey(sym),
offset: SlotOffset {
class: SlotClass::Inline,
word_offset,
bit_width,
elem_word_count: 1,
row_stride_words: 0,
num_rows: 0,
},
initial: None,
net_info: None,
}
}
#[test]
fn allocate_instance_threads_memory_row_stride_and_num_rows_into_cells() {
let templates = TemplateRegistry::new();
let tmpl = key(0, 0);
let memory_slot = SlotDescriptor {
key: SymbolKey(0),
offset: SlotOffset {
class: SlotClass::Memory,
word_offset: 0,
bit_width: 32,
elem_word_count: 3,
row_stride_words: 5,
num_rows: 9,
},
initial: None,
net_info: None,
};
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![memory_slot],
inline_words: 0,
wide_words: 3 * 9,
},
);
}
let mut engine = StateEngine::new(templates);
engine.allocate_instance(tmpl).expect("template registered");
assert_eq!(engine.cells.len(), 1);
let loc = engine.cells[0];
assert_eq!(loc.class, SlotClass::Memory);
assert_eq!(loc.elem_word_count, 3);
assert_eq!(
loc.row_stride_words, 5,
"allocate_instance must copy row_stride_words from the SlotOffset into self.cells, not drop/zero it"
);
assert_eq!(
loc.num_rows, 9,
"allocate_instance must copy num_rows from the SlotOffset into self.cells, not drop/zero it"
);
}
#[test]
fn second_pass_assigns_distinct_nonoverlapping_bases_for_8_instances() {
let templates = TemplateRegistry::new();
let tmpl = key(0, 0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let bases: Vec<u32> = (0..8)
.map(|_| {
engine
.allocate_instance(tmpl)
.expect("template is registered")
.base
.inline_base
})
.collect();
assert_eq!(bases, vec![0, 1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn slot_of_is_template_relative_and_instance_independent() {
let templates = TemplateRegistry::new();
let tmpl = key(1, 0);
let sym = SymbolKey(7);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(7, 0, 4)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let before = engine
.slot_of(&tmpl, sym)
.expect("slot exists before allocation");
for _ in 0..3 {
engine
.allocate_instance(tmpl)
.expect("template is registered");
}
let after = engine
.slot_of(&tmpl, sym)
.expect("slot exists after allocation");
assert_eq!(before, after, "slot_of must be instance-independent");
}
#[test]
fn template_size_matches_the_built_layouts_word_counts() {
let templates = TemplateRegistry::new();
let tmpl = key(2, 0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![
inline_slot(0, 0, 8),
SlotDescriptor {
key: SymbolKey(1),
offset: SlotOffset {
class: SlotClass::Wide,
word_offset: 0,
bit_width: 128,
elem_word_count: 2,
row_stride_words: 0,
num_rows: 0,
},
initial: None,
net_info: None,
},
],
inline_words: 1,
wide_words: 2,
},
);
}
let engine = StateEngine::new(templates);
assert_eq!(engine.template_size(&tmpl), Some((1, 2)));
}
#[test]
fn allocate_instance_n_times_yields_n_handles_that_never_shrink_or_reuse() {
let templates = TemplateRegistry::new();
let tmpl_a = key(3, 0);
let tmpl_b = key(4, 0);
unsafe {
templates.register(
tmpl_a,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
templates.register(
tmpl_b,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let h1 = engine.allocate_instance(tmpl_a).expect("tmpl_a registered");
let h2 = engine.allocate_instance(tmpl_b).expect("tmpl_b registered");
let h3 = engine.allocate_instance(tmpl_a).expect("tmpl_a registered");
let bases: Vec<(u32, u32)> = [&h1, &h2, &h3]
.iter()
.map(|h| (h.base.inline_base, h.base.wide_base))
.collect();
assert_ne!(bases[0], bases[1]);
assert_ne!(bases[1], bases[2]);
assert_ne!(bases[0], bases[2]);
}
#[test]
fn slot_offset_has_no_external_constructor_outside_the_layout_builder() {
let templates = TemplateRegistry::new();
let tmpl = key(5, 0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
let engine = StateEngine::new(templates);
assert!(engine.slot_of(&tmpl, SymbolKey(999)).is_none());
assert!(engine.slot_of(&key(999, 0), SymbolKey(0)).is_none());
}
#[test]
fn moving_the_state_engine_after_allocating_an_instance_leaves_the_handle_valid() {
let templates = TemplateRegistry::new();
let tmpl = key(6, 0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let handle = engine
.allocate_instance(tmpl)
.expect("template is registered");
const PATTERN: u128 = 0xDEAD_BEEF_CAFE_F00D_0123_4567_89AB_CDEF;
unsafe {
engine.arena.inline_mut()[handle.base.inline_base as usize] = PATTERN;
}
let relocated = Box::new(engine);
let engine = *relocated;
let _ = &engine;
let read_back = unsafe { (*handle.arena).inline()[handle.base.inline_base as usize] };
assert_eq!(
read_back, PATTERN,
"handle must survive StateEngine relocating in memory"
);
}
#[test]
fn allocate_instance_called_many_more_times_leaves_the_first_handles_arena_pointer_valid() {
let templates = TemplateRegistry::new();
let tmpl = key(7, 0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let first = engine
.allocate_instance(tmpl)
.expect("template is registered");
const PATTERN: u128 = 0x1357_9BDF_2468_ACE0_FEDC_BA98_7654_3210;
unsafe {
(*first.arena).inline_mut()[first.base.inline_base as usize] = PATTERN;
}
let mut later_handles = Vec::new();
for _ in 0..4096 {
later_handles.push(
engine
.allocate_instance(tmpl)
.expect("template is registered"),
);
}
let read_back = unsafe { (*first.arena).inline()[first.base.inline_base as usize] };
assert_eq!(
read_back, PATTERN,
"first handle's arena pointer must survive many later allocate_instance calls"
);
assert_eq!(later_handles.len(), 4096);
}
#[test]
fn registering_more_templates_after_allocating_an_instance_leaves_the_layout_pointer_valid() {
let templates = TemplateRegistry::new();
let tmpl_a = key(8, 0);
let expected_offset = inline_slot(0, 0, 1).offset;
unsafe {
templates.register(
tmpl_a,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let handle = engine
.allocate_instance(tmpl_a)
.expect("tmpl_a is registered");
unsafe {
for i in 0..10_000u32 {
engine.templates.register(
key(100 + i, 0),
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
}
let layout_via_handle = unsafe { &*handle.layout };
assert_eq!(
layout_via_handle.inline_words, 1,
"layout pointer must survive further register() calls after allocate_instance"
);
assert_eq!(layout_via_handle.wide_words, 0);
assert_eq!(layout_via_handle.slots.len(), 1);
assert_eq!(layout_via_handle.slots[0].key, SymbolKey(0));
assert_eq!(layout_via_handle.slots[0].offset, expected_offset);
assert_eq!(engine.template_size(&tmpl_a), Some((1, 0)));
}
#[test]
fn dropping_the_state_engine_after_allocating_an_instance_leaves_the_handle_valid() {
const PATTERN: u128 = 0x0F1E_2D3C_4B5A_6978_8796_A5B4_C3D2_E1F0;
let handle = {
let templates = TemplateRegistry::new();
let tmpl = key(9, 0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let handle = engine
.allocate_instance(tmpl)
.expect("template is registered");
unsafe {
engine.arena.inline_mut()[handle.base.inline_base as usize] = PATTERN;
}
drop(engine);
handle
};
let read_back = unsafe { (*handle.arena).inline()[handle.base.inline_base as usize] };
assert_eq!(
read_back, PATTERN,
"handle must survive the StateEngine that minted it being dropped outright"
);
}
#[test]
fn class_static_instance_is_deterministic_across_repeat_calls() {
let id = ClassTemplateId(3);
assert_eq!(class_static_instance(id), class_static_instance(id));
}
#[test]
fn class_static_instance_is_injective_over_distinct_class_templates() {
let (a_inst, a_key) = class_static_instance(ClassTemplateId(0));
let (b_inst, b_key) = class_static_instance(ClassTemplateId(1));
assert_ne!(a_inst, b_inst);
assert_ne!(a_key, b_key);
}
#[test]
fn class_static_instance_stays_clear_of_the_bank_reserved_id() {
const BANK_RESERVED: u32 = u32::MAX - (1 << 20);
for raw in [0u32, 1, 1000, 1 << 19] {
let (_, key) = class_static_instance(ClassTemplateId(raw));
assert!(
key.definition < BANK_RESERVED,
"ClassTemplateId({raw})'s minted definition {} must stay below \
the bank's reserved id {BANK_RESERVED}",
key.definition
);
}
}
#[test]
#[should_panic(expected = "exceeds the reserved")]
fn class_static_instance_panics_rather_than_wraps_past_the_reserved_block() {
let _ = class_static_instance(ClassTemplateId(u32::MAX));
}
#[test]
fn class_static_instance_params_hash_is_not_the_banks_or_a_packages() {
let (_, key) = class_static_instance(ClassTemplateId(0));
assert_ne!(key.params_hash, 1, "1 is the bank's own params_hash");
assert_ne!(key.params_hash, 0, "0 is every package's own params_hash");
}
#[test]
fn allocate_class_static_singletons_gives_each_class_template_its_own_storage() {
let templates = TemplateRegistry::new();
let mut engine = StateEngine::new(templates);
let layout_of = || StateLayout {
slots: vec![inline_slot(0, 0, 32)],
inline_words: 1,
wide_words: 0,
};
let a = ClassTemplateId(0);
let b = ClassTemplateId(1);
let mut handles =
engine.allocate_class_static_singletons(vec![(a, layout_of()), (b, layout_of())]);
let (_, handle_a) = handles.remove(&a).expect("class template a allocated");
let (_, handle_b) = handles.remove(&b).expect("class template b allocated");
unsafe {
engine.arena.inline_mut()[handle_a.base.inline_base as usize] = 11;
engine.arena.inline_mut()[handle_b.base.inline_base as usize] = 22;
}
assert_eq!(
engine.arena.inline()[handle_a.base.inline_base as usize],
11
);
assert_eq!(
engine.arena.inline()[handle_b.base.inline_base as usize],
22,
"class template b's static-property storage must be untouched by \
a write through class template a's own singleton handle"
);
}
#[test]
fn allocate_class_static_singletons_write_is_visible_to_every_future_reader() {
let templates = TemplateRegistry::new();
let mut engine = StateEngine::new(templates);
let class_template = ClassTemplateId(0);
let layout = StateLayout {
slots: vec![inline_slot(0, 0, 32)],
inline_words: 1,
wide_words: 0,
};
let mut handles = engine.allocate_class_static_singletons(vec![(class_template, layout)]);
let (_, handle) = handles.remove(&class_template).unwrap();
let slot = handle.base.inline_base as usize;
unsafe {
engine.arena.inline_mut()[slot] = 0;
engine.arena.inline_mut()[slot] += 1;
}
assert_eq!(
engine.arena.inline()[slot],
1,
"a second reader of the same class template's static property must \
observe the first reader's write, since both resolve to the same \
singleton handle"
);
}
#[test]
fn allocate_class_static_singletons_instance_id_matches_class_static_instance() {
let templates = TemplateRegistry::new();
let mut engine = StateEngine::new(templates);
let class_template = ClassTemplateId(7);
let layout = StateLayout {
slots: vec![inline_slot(0, 0, 1)],
inline_words: 1,
wide_words: 0,
};
let mut handles = engine.allocate_class_static_singletons(vec![(class_template, layout)]);
let (instance_id, _) = handles.remove(&class_template).unwrap();
let (expected_instance_id, _) = class_static_instance(class_template);
assert_eq!(instance_id, expected_instance_id);
}
#[test]
fn allocate_class_static_singletons_with_no_layouts_allocates_nothing() {
let templates = TemplateRegistry::new();
let mut engine = StateEngine::new(templates);
let handles = engine.allocate_class_static_singletons(Vec::new());
assert!(handles.is_empty());
}
}