use std::collections::{HashMap, HashSet};
use std::sync::{Mutex, OnceLock};
use crate::constraints::randc::RandcState;
use crate::netlist::ClassTemplateId;
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct FieldSlot {
pub offset: usize,
pub words: usize,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub struct Window {
pub bit: u32,
pub width: u32,
pub field_width: u32,
}
fn get_bits(words: &[u64], lo: usize, n: usize) -> u64 {
debug_assert!(n <= 64);
if n == 0 {
return 0;
}
let w = lo / 64;
let sh = lo % 64;
let a = words.get(w).copied().unwrap_or(0) >> sh;
let v = if sh == 0 {
a
} else {
a | (words.get(w + 1).copied().unwrap_or(0) << (64 - sh))
};
if n == 64 { v } else { v & ((1u64 << n) - 1) }
}
fn set_bits(words: &mut [u64], lo: usize, n: usize, v: u64) {
debug_assert!(n <= 64);
if n == 0 {
return;
}
let mask = if n == 64 { u64::MAX } else { (1u64 << n) - 1 };
let v = v & mask;
let w = lo / 64;
let sh = lo % 64;
if let Some(x) = words.get_mut(w) {
*x = (*x & !(mask << sh)) | (v << sh);
}
if sh != 0
&& sh + n > 64
&& let Some(x) = words.get_mut(w + 1)
{
let hi_mask = mask >> (64 - sh);
*x = (*x & !hi_mask) | (v >> (64 - sh));
}
}
impl Window {
fn read(&self, fields: &[u64], val: &mut [u64], unk: &mut [u64]) {
for (i, (v, u)) in val.iter_mut().zip(unk.iter_mut()).enumerate() {
let lo = i * 64;
let n = (self.width as usize).saturating_sub(lo).min(64);
*v = get_bits(fields, self.bit as usize + lo, n);
*u = get_bits(fields, (self.field_width + self.bit) as usize + lo, n);
}
}
fn write(&self, fields: &mut [u64], val: &[u64]) {
for i in 0..(self.width as usize).div_ceil(64) {
let lo = i * 64;
let n = (self.width as usize - lo).min(64);
let v = val.get(i).copied().unwrap_or(0);
set_bits(fields, self.bit as usize + lo, n, v);
set_bits(fields, (self.field_width + self.bit) as usize + lo, n, 0);
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct NestedHandle {
pub slot: FieldSlot,
pub bit: u32,
pub coll: bool,
}
fn nested_get(fields: &[u64], nh: &NestedHandle) -> Option<u64> {
let w = nh.slot.offset + (nh.bit / 64) as usize;
let sh = nh.bit % 64;
let lo = *fields.get(w)? >> sh;
if sh == 0 {
return Some(lo);
}
Some(lo | (*fields.get(w + 1)? << (64 - sh)))
}
fn nested_set(fields: &mut [u64], nh: &NestedHandle, h: u64) {
let w = nh.slot.offset + (nh.bit / 64) as usize;
let sh = nh.bit % 64;
if sh == 0 {
if let Some(d) = fields.get_mut(w) {
*d = h;
}
return;
}
if w + 1 >= fields.len() {
return;
}
let lo_mask = u64::MAX << sh;
fields[w] = (fields[w] & !lo_mask) | (h << sh);
fields[w + 1] = (fields[w + 1] & lo_mask) | (h >> (64 - sh));
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RandKind {
Rand,
RandC,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RandProp {
pub slot: FieldSlot,
pub kind: RandKind,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RandMeta {
pub rand_mode: Vec<bool>,
pub leaf_off: Vec<(usize, u32)>,
pub leaf_on: Vec<(usize, u32)>,
}
struct ObjEntry {
version: u32,
template: ClassTemplateId,
refcount: u32,
fields: Box<[u64]>,
prng_seed: u64,
rand_meta: RandMeta,
randc: HashMap<usize, RandcState>,
static_rand_off: HashSet<usize>,
constraint_off: HashSet<u32>,
}
struct Table {
entries: Vec<Option<ObjEntry>>,
versions: Vec<u32>,
free: Vec<u32>,
}
fn table() -> &'static Mutex<Table> {
static T: OnceLock<Mutex<Table>> = OnceLock::new();
T.get_or_init(|| {
Mutex::new(Table {
entries: vec![None],
versions: vec![0],
free: Vec::new(),
})
})
}
#[derive(Default, Clone)]
struct TemplateInfo {
handle_fields: Vec<FieldSlot>,
collection_fields: Vec<FieldSlot>,
nested_handles: Vec<NestedHandle>,
ancestors: HashSet<ClassTemplateId>,
rand_props: Vec<RandProp>,
}
fn registry() -> &'static Mutex<HashMap<ClassTemplateId, TemplateInfo>> {
static R: OnceLock<Mutex<HashMap<ClassTemplateId, TemplateInfo>>> = OnceLock::new();
R.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn register_handle_fields(template: ClassTemplateId, slots: Vec<FieldSlot>) {
let mut r = registry().lock().unwrap_or_else(|e| e.into_inner());
r.entry(template).or_default().handle_fields = slots;
}
pub fn register_collection_fields(template: ClassTemplateId, slots: Vec<FieldSlot>) {
let mut r = registry().lock().unwrap_or_else(|e| e.into_inner());
r.entry(template).or_default().collection_fields = slots;
}
pub fn register_nested_handles(template: ClassTemplateId, nested: Vec<NestedHandle>) {
let mut r = registry().lock().unwrap_or_else(|e| e.into_inner());
r.entry(template).or_default().nested_handles = nested;
}
pub fn register_ancestors(template: ClassTemplateId, mut ancestors: HashSet<ClassTemplateId>) {
ancestors.insert(template);
let mut r = registry().lock().unwrap_or_else(|e| e.into_inner());
r.entry(template).or_default().ancestors = ancestors;
}
pub fn register_rand_props(template: ClassTemplateId, props: Vec<RandProp>) {
let mut r = registry().lock().unwrap_or_else(|e| e.into_inner());
r.entry(template).or_default().rand_props = props;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TemplateSnapshot {
pub id: ClassTemplateId,
pub handle_fields: Vec<FieldSlot>,
pub collection_fields: Vec<FieldSlot>,
pub nested_handles: Vec<NestedHandle>,
pub ancestors: Vec<ClassTemplateId>,
pub rand_props: Vec<RandProp>,
}
pub fn template_snapshot(ids: &[ClassTemplateId]) -> Vec<TemplateSnapshot> {
let mut ids = ids.to_vec();
ids.sort_by_key(|i| i.0);
ids.dedup();
let r = registry().lock().unwrap_or_else(|e| e.into_inner());
ids.into_iter()
.filter_map(|id| {
let info = r.get(&id)?;
let mut ancestors: Vec<ClassTemplateId> = info.ancestors.iter().copied().collect();
ancestors.sort_by_key(|i| i.0);
Some(TemplateSnapshot {
id,
handle_fields: info.handle_fields.clone(),
collection_fields: info.collection_fields.clone(),
nested_handles: info.nested_handles.clone(),
ancestors,
rand_props: info.rand_props.clone(),
})
})
.collect()
}
pub fn preload_templates(snapshot: &[TemplateSnapshot]) {
let mut r = registry().lock().unwrap_or_else(|e| e.into_inner());
for t in snapshot {
let info = r.entry(t.id).or_default();
info.handle_fields = t.handle_fields.clone();
info.collection_fields = t.collection_fields.clone();
info.nested_handles = t.nested_handles.clone();
info.ancestors = t.ancestors.iter().copied().collect();
info.rand_props = t.rand_props.clone();
}
}
pub fn rand_props(template: ClassTemplateId) -> Vec<RandProp> {
registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&template)
.map(|info| info.rand_props.clone())
.unwrap_or_default()
}
fn index(h: u64) -> usize {
(h & 0xffff_ffff) as usize
}
fn version_of(h: u64) -> u32 {
(h >> 32) as u32
}
impl Table {
fn alloc(&mut self, template: ClassTemplateId, field_words: usize) -> u64 {
let i = match self.free.pop() {
Some(i) => i as usize,
None => {
self.entries.push(None);
self.versions.push(0);
self.entries.len() - 1
}
};
let version = self.versions[i].wrapping_add(1);
self.versions[i] = version;
let rand_mode = vec![
true;
registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&template)
.map_or(0, |info| info.rand_props.len())
];
self.entries[i] = Some(ObjEntry {
version,
template,
refcount: 1,
fields: vec![0u64; field_words].into(),
prng_seed: 0,
rand_meta: RandMeta {
rand_mode,
leaf_off: Vec::new(),
leaf_on: Vec::new(),
},
randc: HashMap::new(),
static_rand_off: HashSet::new(),
constraint_off: HashSet::new(),
});
(u64::from(version) << 32) | i as u64
}
fn get(&self, h: u64) -> Option<&ObjEntry> {
let i = index(h);
if i == 0 {
return None;
}
let e = self.entries.get(i)?.as_ref()?;
(e.version == version_of(h)).then_some(e)
}
fn get_mut(&mut self, h: u64) -> Option<&mut ObjEntry> {
let i = index(h);
if i == 0 {
return None;
}
let v = version_of(h);
let e = self.entries.get_mut(i)?.as_mut()?;
(e.version == v).then_some(e)
}
}
pub fn alloc(template: ClassTemplateId, field_words: usize) -> u64 {
ALLOCS_SINCE_GC.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let h = table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.alloc(template, field_words);
if let Some(hook) = CREATE_HOOK.get() {
hook(h);
}
h
}
static CREATE_HOOK: std::sync::OnceLock<fn(u64)> = std::sync::OnceLock::new();
static RECLAIM_HOOK: std::sync::OnceLock<fn(u64)> = std::sync::OnceLock::new();
pub fn set_lifecycle_hooks(create: fn(u64), reclaim: fn(u64)) {
let _ = CREATE_HOOK.set(create);
let _ = RECLAIM_HOOK.set(reclaim);
}
pub fn retain(h: u64) {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
if let Some(e) = t.get_mut(h) {
e.refcount = e.refcount.saturating_add(1);
}
}
pub fn release(h: u64) {
let mut pending = vec![h];
let mut freed = false;
let mut colls: Vec<u64> = Vec::new();
while let Some(h) = pending.pop() {
{
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { continue };
e.refcount = e.refcount.saturating_sub(1);
if e.refcount > 0 {
continue;
}
}
if let Some(hook) = RECLAIM_HOOK.get() {
hook(h);
}
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { continue };
if e.refcount > 0 {
continue;
}
let template = e.template;
{
let r = registry().lock().unwrap_or_else(|e| e.into_inner());
if let Some(info) = r.get(&template) {
for slot in &info.handle_fields {
if let Some(&w) = e.fields.get(slot.offset) {
pending.push(w);
}
}
for slot in &info.collection_fields {
if let Some(&w) = e.fields.get(slot.offset) {
colls.push(w);
}
}
for nh in &info.nested_handles {
if let Some(w) = nested_get(&e.fields, nh) {
if nh.coll {
colls.push(w);
} else {
pending.push(w);
}
}
}
}
}
let i = index(h);
t.entries[i] = None;
t.free.push(i as u32);
freed = true;
}
for c in colls {
crate::collections::release(c);
}
if freed {
crate::sched::notify_object_state_change();
}
}
pub fn copy(h: u64) -> Option<u64> {
let (template, fields, rand_meta, constraint_off, prng_seed, colls, nested_hs) = {
let t = table().lock().unwrap_or_else(|e| e.into_inner());
let src = t.get(h)?;
let (colls, nested_hs): (Vec<usize>, Vec<NestedHandle>) = registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&src.template)
.map(|info| {
(
info.collection_fields.iter().map(|s| s.offset).collect(),
info.nested_handles.clone(),
)
})
.unwrap_or_default();
(
src.template,
src.fields.clone(),
src.rand_meta.clone(),
src.constraint_off.clone(),
src.prng_seed,
colls,
nested_hs,
)
};
let mut fields = fields;
for off in colls {
if let Some(w) = fields.get_mut(off) {
*w = crate::collections::clone(*w);
}
}
let mut nested_objs: Vec<u64> = Vec::new();
for nh in &nested_hs {
let Some(w) = nested_get(&fields, nh) else {
continue;
};
if nh.coll {
nested_set(&mut fields, nh, crate::collections::clone(w));
} else {
nested_objs.push(w);
}
}
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let mut nested: Vec<u64> = registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&template)
.map(|info| {
info.handle_fields
.iter()
.filter_map(|s| fields.get(s.offset).copied())
.collect()
})
.unwrap_or_default();
nested.extend(nested_objs);
let new = t.alloc(template, fields.len());
for n in nested {
if let Some(e) = t.get_mut(n) {
e.refcount = e.refcount.saturating_add(1);
}
}
if let Some(e) = t.get_mut(new) {
e.fields = fields;
e.rand_meta = rand_meta;
e.constraint_off = constraint_off;
e.prng_seed = prng_seed;
}
drop(t);
if let Some(hook) = CREATE_HOOK.get() {
hook(new);
}
Some(new)
}
pub fn live_count() -> usize {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.entries
.iter()
.filter(|e| e.is_some())
.count()
}
pub fn refcount(h: u64) -> Option<u32> {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.map(|e| e.refcount)
}
pub fn field_get(h: u64, slot: FieldSlot, out: &mut [u64]) -> bool {
let t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get(h) else { return false };
if slot.offset + slot.words > e.fields.len() {
return false;
}
let n = out.len().min(slot.words);
out[..n].copy_from_slice(&e.fields[slot.offset..slot.offset + n]);
true
}
pub fn field_set(h: u64, slot: FieldSlot, val: &[u64]) -> bool {
let changed = {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
if slot.offset + slot.words > e.fields.len() {
return false;
}
let n = val.len().min(slot.words);
let dest = &mut e.fields[slot.offset..slot.offset + n];
let changed = dest != &val[..n];
dest.copy_from_slice(&val[..n]);
changed
};
if changed {
crate::sched::notify_object_state_change();
}
true
}
pub fn field_merge(h: u64, offset: usize, val: &[u64], mask: &[u64]) -> bool {
let changed = {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
let n = val.len().min(mask.len());
if offset + n > e.fields.len() {
return false;
}
let mut changed = false;
for i in 0..n {
let old = e.fields[offset + i];
let new = (old & !mask[i]) | (val[i] & mask[i]);
changed |= new != old;
e.fields[offset + i] = new;
}
changed
};
if changed {
crate::sched::notify_object_state_change();
}
true
}
pub fn field_get_window(
h: u64,
slot: FieldSlot,
win: Window,
val: &mut [u64],
unk: &mut [u64],
) -> bool {
let t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get(h) else { return false };
if slot.offset + slot.words > e.fields.len() {
return false;
}
win.read(&e.fields[slot.offset..slot.offset + slot.words], val, unk);
true
}
pub fn field_set_window(h: u64, slot: FieldSlot, win: Window, val: &[u64]) -> bool {
let changed = {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
if slot.offset + slot.words > e.fields.len() {
return false;
}
let dest = &mut e.fields[slot.offset..slot.offset + slot.words];
let before = dest.to_vec();
win.write(dest, val);
before != dest
};
if changed {
crate::sched::notify_object_state_change();
}
true
}
pub fn is_live(h: u64) -> bool {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.is_some()
}
pub fn slot_index(h: u64) -> u64 {
index(h) as u64
}
pub fn dynamic_template(h: u64) -> Option<ClassTemplateId> {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.map(|e| e.template)
}
pub fn is_a(h: u64, dest: ClassTemplateId) -> bool {
let Some(template) = dynamic_template(h) else {
return false;
};
template_is_a(template, dest)
}
pub fn template_is_a(template: ClassTemplateId, dest: ClassTemplateId) -> bool {
registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&template)
.is_some_and(|info| info.ancestors.contains(&dest))
}
pub fn prng_seed(h: u64) -> Option<u64> {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.map(|e| e.prng_seed)
}
pub fn set_prng_seed(h: u64, seed: u64) {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
if let Some(e) = t.get_mut(h) {
e.prng_seed = seed;
}
}
pub fn rand_meta(h: u64) -> Option<RandMeta> {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.map(|e| e.rand_meta.clone())
}
pub fn set_rand_meta(h: u64, meta: RandMeta) {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
if let Some(e) = t.get_mut(h) {
e.rand_meta = meta;
}
}
static ALLOCS_SINCE_GC: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub fn collect_cycles() -> usize {
let freed = collect_cycles_locked();
COLLECTED.fetch_add(freed, std::sync::atomic::Ordering::Relaxed);
freed
}
static COLLECTED: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub fn collected_count() -> usize {
COLLECTED.load(std::sync::atomic::Ordering::Relaxed)
}
fn collect_cycles_locked() -> usize {
ALLOCS_SINCE_GC.store(0, std::sync::atomic::Ordering::Relaxed);
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let r = registry().lock().unwrap_or_else(|e| e.into_inner());
let handle_fields = |e: &ObjEntry| -> Vec<u64> {
r.get(&e.template)
.map(|info| {
info.handle_fields
.iter()
.filter_map(|s| e.fields.get(s.offset).copied())
.collect()
})
.unwrap_or_default()
};
let live: Vec<u64> = t
.entries
.iter()
.enumerate()
.filter_map(|(i, e)| e.as_ref().map(|e| (u64::from(e.version) << 32) | i as u64))
.collect();
let mut internal: HashMap<usize, u32> = HashMap::new();
for &h in &live {
let Some(e) = t.get(h) else { continue };
for w in handle_fields(e) {
if t.get(w).is_some() {
*internal.entry(index(w)).or_default() += 1;
}
}
}
let mut marked: HashSet<usize> = HashSet::new();
let mut stack: Vec<u64> = live
.iter()
.copied()
.filter(|&h| {
t.get(h)
.is_some_and(|e| e.refcount > internal.get(&index(h)).copied().unwrap_or(0))
})
.collect();
while let Some(h) = stack.pop() {
if !marked.insert(index(h)) {
continue;
}
if let Some(e) = t.get(h) {
stack.extend(handle_fields(e).into_iter().filter(|&w| t.get(w).is_some()));
}
}
let garbage: Vec<u64> = live
.into_iter()
.filter(|&h| !marked.contains(&index(h)))
.collect();
let mut drops: Vec<u64> = Vec::new();
for &g in &garbage {
if let Some(e) = t.get(g) {
drops.extend(
handle_fields(e)
.into_iter()
.filter(|w| marked.contains(&index(*w))),
);
}
}
for w in drops {
if let Some(e) = t.get_mut(w) {
e.refcount = e.refcount.saturating_sub(1);
}
}
for &g in &garbage {
let i = index(g);
t.entries[i] = None;
t.free.push(i as u32);
}
garbage.len()
}
pub fn maybe_collect_cycles() -> usize {
let since = ALLOCS_SINCE_GC.load(std::sync::atomic::Ordering::Relaxed);
if since < 4096 || since < LIVE_AFTER_GC.load(std::sync::atomic::Ordering::Relaxed) {
return 0;
}
let freed = collect_cycles();
LIVE_AFTER_GC.store(live_count(), std::sync::atomic::Ordering::Relaxed);
freed
}
static LIVE_AFTER_GC: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub fn rand_mode_of(h: u64, offset: usize) -> Option<bool> {
let t = table().lock().unwrap_or_else(|e| e.into_inner());
let e = t.get(h)?;
let i = rand_prop_index(e.template, offset)?;
e.rand_meta.rand_mode.get(i).copied()
}
pub fn set_rand_mode_of(h: u64, offset: usize, on: bool) -> bool {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
let Some(i) = rand_prop_index(e.template, offset) else {
return false;
};
match e.rand_meta.rand_mode.get_mut(i) {
Some(bit) => {
*bit = on;
e.rand_meta.leaf_off.retain(|&(p, _)| p != i);
e.rand_meta.leaf_on.retain(|&(p, _)| p != i);
true
}
None => false,
}
}
pub fn leaf_rand_mode_of(h: u64, offset: usize, bit: u32) -> Option<bool> {
let t = table().lock().unwrap_or_else(|e| e.into_inner());
let e = t.get(h)?;
let i = rand_prop_index(e.template, offset)?;
let on = *e.rand_meta.rand_mode.get(i)?;
let m = &e.rand_meta;
Some(if on {
!m.leaf_off.contains(&(i, bit))
} else {
m.leaf_on.contains(&(i, bit))
})
}
pub fn set_leaf_rand_mode_of(h: u64, offset: usize, bit: u32, on: bool) -> bool {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
let Some(i) = rand_prop_index(e.template, offset) else {
return false;
};
let m = &mut e.rand_meta;
if i >= m.rand_mode.len() {
return false;
}
m.leaf_off.retain(|&k| k != (i, bit));
m.leaf_on.retain(|&k| k != (i, bit));
if m.rand_mode[i] && !on {
m.leaf_off.push((i, bit));
} else if !m.rand_mode[i] && on {
m.leaf_on.push((i, bit));
}
true
}
pub fn set_all_rand_modes(h: u64, on: bool) -> bool {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
e.rand_meta.rand_mode.iter_mut().for_each(|b| *b = on);
e.rand_meta.leaf_off.clear();
e.rand_meta.leaf_on.clear();
true
}
fn rand_prop_index(template: ClassTemplateId, offset: usize) -> Option<usize> {
registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&template)?
.rand_props
.iter()
.position(|p| p.slot.offset == offset)
}
pub fn static_rand_mode(h: u64, offset: usize) -> Option<bool> {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.map(|e| !e.static_rand_off.contains(&offset))
}
pub fn set_static_rand_mode(h: u64, offset: usize, on: bool) -> bool {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
if on {
e.static_rand_off.remove(&offset);
} else {
e.static_rand_off.insert(offset);
}
true
}
pub fn constraint_mode_of(h: u64, ordinal: u32) -> Option<bool> {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.map(|e| !e.constraint_off.contains(&ordinal))
}
fn static_singleton_keys() -> &'static Mutex<HashMap<String, ClassTemplateId>> {
static K: OnceLock<Mutex<HashMap<String, ClassTemplateId>>> = OnceLock::new();
K.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn register_static_singleton_key(key: &str, template: ClassTemplateId) {
static_singleton_keys()
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(key.to_owned(), template);
}
pub fn static_singleton_key_pairs() -> Vec<(String, u32)> {
let mut v: Vec<(String, u32)> = static_singleton_keys()
.lock()
.unwrap_or_else(|e| e.into_inner())
.iter()
.map(|(k, t)| (k.clone(), t.0))
.collect();
v.sort();
v
}
pub fn static_singleton_of(key: &str) -> Option<Option<u64>> {
let template = *static_singleton_keys()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(key)?;
let t = table().lock().unwrap_or_else(|e| e.into_inner());
Some(t.entries.iter().enumerate().find_map(|(i, e)| {
let e = e.as_ref()?;
(e.template == template).then(|| (u64::from(e.version) << 32) | i as u64)
}))
}
pub fn set_constraint_mode_of(h: u64, ordinal: u32, on: bool) -> bool {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
if on {
e.constraint_off.remove(&ordinal);
} else {
e.constraint_off.insert(ordinal);
}
true
}
pub fn randc_state(h: u64, offset: usize) -> Option<RandcState> {
table()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(h)
.map(|e| e.randc.get(&offset).cloned().unwrap_or_default())
}
pub fn set_randc_states(h: u64, states: Vec<(usize, RandcState)>) -> bool {
let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
let Some(e) = t.get_mut(h) else { return false };
e.randc.extend(states);
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_live_tracks_release_and_rejects_a_stale_handle_after_slot_reuse() {
assert!(!is_live(0), "the null handle is never live");
let a = alloc(ClassTemplateId(1100), 0);
assert!(is_live(a));
release(a);
assert!(!is_live(a), "a freed handle is not live");
let b = alloc(ClassTemplateId(1101), 0);
assert!(is_live(b));
assert!(!is_live(a), "a stale handle must not alias the newcomer");
release(b);
}
#[test]
fn slot_index_is_nonzero_distinct_and_zero_for_null() {
assert_eq!(slot_index(0), 0);
let a = alloc(ClassTemplateId(1102), 0);
let b = alloc(ClassTemplateId(1103), 0);
assert_ne!(slot_index(a), 0);
assert_ne!(slot_index(a), slot_index(b));
release(a);
release(b);
}
#[test]
fn alloc_field_set_field_get_round_trips() {
let h = alloc(ClassTemplateId(1000), 2);
let slot0 = FieldSlot {
offset: 0,
words: 1,
};
let slot1 = FieldSlot {
offset: 1,
words: 1,
};
field_set(h, slot0, &[42]);
field_set(h, slot1, &[7]);
let mut out = [0u64];
assert!(field_get(h, slot0, &mut out));
assert_eq!(out, [42]);
assert!(field_get(h, slot1, &mut out));
assert_eq!(out, [7]);
assert_eq!(dynamic_template(h), Some(ClassTemplateId(1000)));
}
#[test]
fn two_retains_need_two_releases_before_the_slot_frees() {
let h = alloc(ClassTemplateId(1001), 1);
let slot = FieldSlot {
offset: 0,
words: 1,
};
field_set(h, slot, &[0xdead_beef]);
retain(h); release(h); let mut out = [0u64];
assert!(
field_get(h, slot, &mut out),
"one release of two retains must not free the slot"
);
assert_eq!(
out,
[0xdead_beef],
"a leaked (still-referenced) slot's data is intact"
);
release(h); assert!(
!field_get(h, slot, &mut out),
"the second release must free the slot"
);
}
#[test]
fn releasing_an_object_recursively_releases_a_handle_field_it_points() {
let pointee_template = ClassTemplateId(1002);
let pointer_template = ClassTemplateId(1003);
let pointee = alloc(pointee_template, 1);
let pointer = alloc(pointer_template, 1);
let handle_slot = FieldSlot {
offset: 0,
words: 1,
};
field_set(pointer, handle_slot, &[pointee]);
register_handle_fields(pointer_template, vec![handle_slot]);
retain(pointee); assert_eq!(dynamic_template(pointee), Some(pointee_template));
release(pointer); let mut out = [0u64];
assert!(
field_get(pointee, handle_slot, &mut out),
"pointee still has one more reference after the recursive release"
);
release(pointee);
assert!(!field_get(pointee, handle_slot, &mut out));
assert_eq!(dynamic_template(pointee), None);
}
#[test]
fn a_stale_handle_after_its_slot_is_recycled_fails_field_get() {
let mut t = Table {
entries: vec![None],
versions: vec![0],
free: Vec::new(),
};
let h1 = t.alloc(ClassTemplateId(0), 1);
t.get_mut(h1).unwrap().fields[0] = 1;
let i = index(h1);
t.entries[i] = None;
t.free.push(i as u32);
let h2 = t.alloc(ClassTemplateId(1), 1);
assert_eq!(
index(h1),
index(h2),
"the only freed slot in a fresh table is always the next one `alloc` reuses"
);
assert_ne!(
h1, h2,
"alloc must bump the recycled index's version, or the stale handle would still match"
);
assert!(
t.get(h1).is_none(),
"a stale (old_version, index) handle must fail to resolve once its slot is recycled"
);
let e2 = t
.get(h2)
.expect("the new object at the recycled index is live");
assert_eq!(
e2.fields[0], 0,
"the new object's fields start zeroed -- never carrying over the old occupant's data"
);
}
#[test]
fn is_a_matches_self_and_registered_ancestors_only() {
let base = ClassTemplateId(100);
let derived = ClassTemplateId(101);
let unrelated = ClassTemplateId(102);
register_ancestors(derived, HashSet::from([base]));
register_ancestors(base, HashSet::new());
let h = alloc(derived, 0);
assert!(
is_a(h, derived),
"an object is always is_a its own dynamic class"
);
assert!(is_a(h, base), "a derived object is_a its registered base");
assert!(
!is_a(h, unrelated),
"is_a must not match a template that was never registered as an ancestor"
);
let base_h = alloc(base, 0);
assert!(
!is_a(base_h, derived),
"is_a is one-directional: a base object is never is_a its derived class"
);
}
#[test]
fn retain_release_and_field_access_on_the_null_handle_are_safe_no_ops() {
assert_eq!(dynamic_template(0), None);
assert!(!is_a(0, ClassTemplateId(1004)));
retain(0);
release(0);
let slot = FieldSlot {
offset: 0,
words: 1,
};
let mut out = [0u64];
assert!(!field_get(0, slot, &mut out));
field_set(0, slot, &[1]); }
#[test]
fn prng_seed_and_rand_meta_round_trip_and_are_none_once_freed() {
let h = alloc(ClassTemplateId(1005), 0);
assert_eq!(
prng_seed(h),
Some(0),
"freshly allocated, before any seeding hook runs"
);
set_prng_seed(h, 0x1234_5678);
assert_eq!(prng_seed(h), Some(0x1234_5678));
assert_eq!(
rand_meta(h),
Some(RandMeta::default()),
"a template with no registered rand properties has no rand_mode bits"
);
set_rand_meta(
h,
RandMeta {
rand_mode: vec![true, false],
..Default::default()
},
);
assert_eq!(
rand_meta(h),
Some(RandMeta {
rand_mode: vec![true, false],
..Default::default()
})
);
release(h);
assert_eq!(prng_seed(h), None);
assert_eq!(rand_meta(h), None);
}
#[test]
fn rand_props_are_template_level_and_every_object_starts_all_active() {
let t = ClassTemplateId(1040);
let a = RandProp {
slot: FieldSlot {
offset: 0,
words: 1,
},
kind: RandKind::Rand,
};
let b = RandProp {
slot: FieldSlot {
offset: 2,
words: 2,
},
kind: RandKind::RandC,
};
assert_eq!(rand_props(t), Vec::new(), "nothing registered yet");
register_rand_props(t, vec![a, b]);
assert_eq!(rand_props(t), vec![a, b]);
let h1 = alloc(t, 4);
let h2 = alloc(t, 4);
let all_on = RandMeta {
rand_mode: vec![true, true],
..Default::default()
};
assert_eq!(rand_meta(h1), Some(all_on.clone()));
set_rand_meta(
h1,
RandMeta {
rand_mode: vec![true, false],
..Default::default()
},
);
assert_eq!(rand_meta(h2), Some(all_on));
let dup = copy(h1).unwrap();
assert_eq!(rand_meta(dup).unwrap().rand_mode, vec![true, false]);
register_rand_props(t, vec![a]);
assert_eq!(rand_props(t), vec![a]);
let h3 = alloc(t, 4);
assert_eq!(rand_meta(h3).unwrap().rand_mode, vec![true]);
for h in [h1, h2, h3, dup] {
release(h);
}
}
fn one_word(offset: usize) -> FieldSlot {
FieldSlot { offset, words: 1 }
}
#[test]
fn refcount_tracks_retain_and_release_and_is_none_when_dead() {
let h = alloc(ClassTemplateId(1006), 1);
assert_eq!(refcount(h), Some(1));
retain(h);
assert_eq!(refcount(h), Some(2));
release(h);
assert_eq!(refcount(h), Some(1));
release(h);
assert_eq!(refcount(h), None, "a freed handle has no count");
assert_eq!(refcount(0), None, "null has no count");
}
#[test]
fn live_count_sees_an_allocation_until_its_last_release() {
let h = alloc(ClassTemplateId(1007), 1);
assert!(live_count() >= 1);
release(h);
assert_eq!(refcount(h), None);
}
#[test]
fn copy_is_shallow_of_the_dynamic_class_and_retains_nested_handles() {
let inner_t = ClassTemplateId(1008);
let outer_t = ClassTemplateId(1009);
register_handle_fields(outer_t, vec![one_word(1)]);
let inner = alloc(inner_t, 1);
field_set(inner, one_word(0), &[5]);
let src = alloc(outer_t, 2);
field_set(src, one_word(0), &[77]);
field_set(src, one_word(1), &[inner]); assert_eq!(refcount(inner), Some(1));
let dup = copy(src).expect("a live source copies");
assert_ne!(dup, src, "a copy is a distinct object");
assert_eq!(dynamic_template(dup), Some(outer_t));
assert_eq!(refcount(dup), Some(1), "the caller owns the copy");
assert_eq!(refcount(src), Some(1), "the source is untouched");
assert_eq!(
refcount(inner),
Some(2),
"the nested object is shared: both owners hold a reference"
);
let mut out = [0u64];
assert!(field_get(dup, one_word(0), &mut out));
assert_eq!(out, [77], "scalar properties are copied by value");
assert!(field_get(dup, one_word(1), &mut out));
assert_eq!(out, [inner], "the handle is copied, not the object");
field_set(dup, one_word(0), &[88]);
assert!(field_get(src, one_word(0), &mut out));
assert_eq!(out, [77]);
release(src);
assert_eq!(refcount(inner), Some(1));
release(dup);
assert_eq!(refcount(inner), None);
}
#[test]
fn copy_keeps_the_prng_seed_and_rand_meta() {
let src = alloc(ClassTemplateId(1010), 0);
set_prng_seed(src, 99);
set_rand_meta(
src,
RandMeta {
rand_mode: vec![false, true],
..Default::default()
},
);
let dup = copy(src).unwrap();
assert_eq!(prng_seed(dup), Some(99));
assert_eq!(
rand_meta(dup),
Some(RandMeta {
rand_mode: vec![false, true],
..Default::default()
})
);
release(src);
release(dup);
}
#[test]
fn leaf_rand_mode_is_per_element_and_reset_by_the_whole_property() {
let t = ClassTemplateId(1031);
register_rand_props(
t,
vec![RandProp {
slot: one_word(0),
kind: RandKind::Rand,
}],
);
let h = alloc(t, 2);
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(true));
assert!(set_leaf_rand_mode_of(h, 0, 8, false));
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(false));
assert_eq!(leaf_rand_mode_of(h, 0, 0), Some(true));
assert_eq!(rand_mode_of(h, 0), Some(true));
assert!(
!set_leaf_rand_mode_of(h, 1, 8, false),
"offset 1 is no rand property"
);
assert!(set_rand_mode_of(h, 0, true));
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(true));
assert!(set_leaf_rand_mode_of(h, 0, 8, false));
assert!(set_all_rand_modes(h, true));
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(true));
assert!(set_rand_mode_of(h, 0, false));
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(false));
assert!(set_leaf_rand_mode_of(h, 0, 8, true));
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(true));
assert_eq!(leaf_rand_mode_of(h, 0, 0), Some(false));
assert!(set_leaf_rand_mode_of(h, 0, 8, false));
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(false));
assert!(set_leaf_rand_mode_of(h, 0, 8, true));
assert!(set_rand_mode_of(h, 0, false));
assert_eq!(leaf_rand_mode_of(h, 0, 8), Some(false), "whole call resets");
release(h);
}
#[test]
fn rand_mode_is_addressed_by_property_offset() {
let t = ClassTemplateId(1030);
register_rand_props(
t,
vec![
RandProp {
slot: one_word(0),
kind: RandKind::Rand,
},
RandProp {
slot: one_word(3),
kind: RandKind::RandC,
},
],
);
let h = alloc(t, 4);
assert_eq!(rand_mode_of(h, 0), Some(true));
assert_eq!(rand_mode_of(h, 3), Some(true));
assert!(set_rand_mode_of(h, 3, false));
assert_eq!(rand_mode_of(h, 0), Some(true));
assert_eq!(rand_mode_of(h, 3), Some(false));
assert_eq!(rand_mode_of(h, 1), None);
assert!(!set_rand_mode_of(h, 1, false));
assert_eq!(rand_meta(h).unwrap().rand_mode, vec![true, false]);
assert!(set_all_rand_modes(h, false));
assert_eq!(rand_meta(h).unwrap().rand_mode, vec![false, false]);
assert!(set_all_rand_modes(h, true));
assert_eq!(rand_meta(h).unwrap().rand_mode, vec![true, true]);
release(h);
assert_eq!(rand_mode_of(h, 0), None);
assert!(!set_rand_mode_of(h, 0, true));
assert!(!set_all_rand_modes(h, true));
}
#[test]
fn static_rand_and_constraint_modes_are_kept_per_holding_object() {
let t = ClassTemplateId(1031);
let h = alloc(t, 2);
assert_eq!(static_rand_mode(h, 1), Some(true));
assert_eq!(constraint_mode_of(h, 2), Some(true));
assert!(set_static_rand_mode(h, 1, false));
assert!(set_constraint_mode_of(h, 2, false));
assert_eq!(static_rand_mode(h, 1), Some(false));
assert_eq!(static_rand_mode(h, 0), Some(true));
assert_eq!(constraint_mode_of(h, 2), Some(false));
assert_eq!(constraint_mode_of(h, 1), Some(true));
let dup = copy(h).unwrap();
assert_eq!(constraint_mode_of(dup, 2), Some(false));
assert!(set_constraint_mode_of(h, 2, true));
assert_eq!(constraint_mode_of(h, 2), Some(true));
assert_eq!(constraint_mode_of(dup, 2), Some(false));
assert!(set_static_rand_mode(h, 1, true));
assert_eq!(static_rand_mode(h, 1), Some(true));
release(h);
release(dup);
assert_eq!(static_rand_mode(h, 1), None);
assert_eq!(constraint_mode_of(h, 2), None);
assert!(!set_static_rand_mode(h, 1, false));
assert!(!set_constraint_mode_of(h, 2, false));
}
#[test]
fn a_collection_field_is_freed_with_its_object_and_cloned_by_copy() {
use crate::collections::{self, KeyKind, Shape};
let shape = Shape {
elem_words: 1,
assoc: false,
key: KeyKind::Int {
width: 32,
signed: true,
},
obj: false,
nested: crate::collections::Nested::NONE,
};
let t = ClassTemplateId(1060);
register_collection_fields(t, vec![one_word(1)]);
let src = alloc(t, 2);
let q = collections::set(&shape, 0, 0, &[7], true);
let q = collections::set(&shape, q, 1, &[8], true);
assert_eq!(collections::size(q), 2);
field_set(src, one_word(1), &[q]);
let dup = copy(src).expect("copies");
let mut out = [0u64];
assert!(field_get(dup, one_word(1), &mut out));
let q2 = out[0];
assert_ne!(q2, q, "the copy has its own collection handle");
assert_eq!(collections::size(q2), 2);
let q2b = collections::set(&shape, q2, 0, &[99], true);
let mut e = [0u64];
assert!(collections::get(q, 0, &mut e));
assert_eq!(e, [7]);
assert!(collections::get(q2b, 0, &mut e));
assert_eq!(e, [99]);
field_set(dup, one_word(1), &[q2b]);
release(src);
assert!(!collections::is_live(q), "freed with the owning object");
assert_eq!(collections::size(q2b), 2, "the copy's collection survives");
release(dup);
assert!(!collections::is_live(q2b));
}
#[test]
fn nested_handles_at_unaligned_bit_offsets_follow_the_object() {
use crate::collections::{self, KeyKind, Shape};
let shape = Shape {
elem_words: 1,
assoc: false,
key: KeyKind::Int {
width: 32,
signed: true,
},
obj: false,
nested: crate::collections::Nested::NONE,
};
let t = ClassTemplateId(1021);
let slot = FieldSlot {
offset: 0,
words: 5,
};
let obj_at = NestedHandle {
slot,
bit: 8,
coll: false,
};
let coll_at = NestedHandle {
slot,
bit: 72,
coll: true,
};
register_nested_handles(t, vec![obj_at, coll_at]);
let leaf = alloc(ClassTemplateId(1022), 1);
let q = collections::set(&shape, 0, 0, &[5], true);
let src = alloc(t, 5);
let mut fields = vec![0u64; 5];
nested_set(&mut fields, &obj_at, leaf);
nested_set(&mut fields, &coll_at, q);
assert_eq!(nested_get(&fields, &obj_at), Some(leaf));
assert_eq!(nested_get(&fields, &coll_at), Some(q), "no overlap clobber");
assert!(field_set(src, slot, &fields));
let dup = copy(src).expect("copies");
assert_eq!(
refcount(leaf),
Some(2),
"the copy retains the nested object"
);
let mut out = vec![0u64; 5];
assert!(field_get(dup, slot, &mut out));
let q2 = nested_get(&out, &coll_at).expect("in range");
assert_ne!(q2, q, "the copy owns a clone of the nested collection");
assert_eq!(collections::size(q2), 1);
assert_eq!(nested_get(&out, &obj_at), Some(leaf));
release(src);
assert_eq!(
refcount(leaf),
Some(1),
"the original dropped its reference"
);
assert!(
!collections::is_live(q),
"the nested collection died with it"
);
release(dup);
assert_eq!(refcount(leaf), None, "the last holder freed it");
assert!(!collections::is_live(q2));
}
#[test]
fn windows_read_and_write_only_their_own_bits() {
let slot = FieldSlot {
offset: 1,
words: 4,
};
let h = alloc(ClassTemplateId(1031), 5);
assert!(field_set(h, slot, &[u64::MAX; 4]));
let wide = Window {
bit: 5,
width: 70,
field_width: 100,
};
let mut val = [0u64; 2];
let mut unk = [0u64; 2];
assert!(field_get_window(h, slot, wide, &mut val, &mut unk));
assert_eq!(val, [u64::MAX, 0x3f]);
assert_eq!(unk, [u64::MAX, 0x3f]);
assert!(field_set_window(
h,
slot,
wide,
&[0x0123_4567_89ab_cdef, 0x15]
));
assert!(field_get_window(h, slot, wide, &mut val, &mut unk));
assert_eq!(val, [0x0123_4567_89ab_cdef, 0x15]);
assert_eq!(unk, [0, 0], "a write makes the leaf known");
let low = Window {
bit: 0,
width: 5,
field_width: 100,
};
let high = Window {
bit: 75,
width: 25,
field_width: 100,
};
let (mut v, mut u) = ([0u64; 1], [0u64; 1]);
assert!(field_get_window(h, slot, low, &mut v, &mut u));
assert_eq!((v[0], u[0]), (0x1f, 0x1f));
assert!(field_get_window(h, slot, high, &mut v, &mut u));
assert_eq!((v[0], u[0]), ((1 << 25) - 1, (1 << 25) - 1));
let one = Window {
bit: 64,
width: 8,
field_width: 100,
};
assert!(field_set_window(h, slot, one, &[0xa5]));
assert!(field_get_window(h, slot, one, &mut v, &mut u));
assert_eq!((v[0], u[0]), (0xa5, 0));
release(h);
assert!(!field_set_window(h, slot, one, &[1]));
assert!(!field_get_window(h, slot, one, &mut v, &mut u));
}
#[test]
fn copy_of_null_or_a_stale_handle_is_none() {
assert_eq!(copy(0), None);
let h = alloc(ClassTemplateId(1011), 1);
release(h);
assert_eq!(copy(h), None);
}
#[test]
fn releasing_the_head_of_a_long_chain_frees_it_without_recursion() {
let t = ClassTemplateId(1012);
register_handle_fields(t, vec![one_word(0)]);
let head = alloc(t, 1);
let mut prev = head;
let mut all = vec![head];
for _ in 0..100_000 {
let next = alloc(t, 1);
field_set(prev, one_word(0), &[next]);
all.push(next);
prev = next;
}
release(head);
assert!(all.iter().all(|&h| refcount(h).is_none()));
}
#[test]
fn a_dropped_cycle_is_collected_and_what_it_shared_survives() {
let t = ClassTemplateId(1900);
let next = FieldSlot {
offset: 0,
words: 1,
};
let other = FieldSlot {
offset: 1,
words: 1,
};
register_handle_fields(t, vec![next, other]);
let a = alloc(t, 2);
let b = alloc(t, 2);
let shared = alloc(t, 2);
field_set(a, next, &[b]);
retain(b);
field_set(b, next, &[a]);
retain(a);
field_set(a, other, &[shared]);
retain(shared);
field_set(b, other, &[shared]);
retain(shared);
release(a);
release(b);
assert_eq!(refcount(a), Some(1), "reference counting alone leaks");
assert_eq!(refcount(shared), Some(3));
let c = alloc(t, 2);
let d = alloc(t, 2);
field_set(c, next, &[d]);
retain(d);
field_set(d, next, &[c]);
retain(c);
release(d);
assert!(collect_cycles() >= 2);
assert_eq!(refcount(a), None);
assert_eq!(refcount(b), None);
assert_eq!(refcount(shared), Some(1));
assert_eq!(refcount(c), Some(2));
assert_eq!(refcount(d), Some(1));
release(c);
assert!(collect_cycles() >= 2);
assert_eq!(refcount(c), None);
assert_eq!(refcount(d), None);
release(shared);
assert_eq!(refcount(shared), None);
}
#[test]
fn randc_state_is_per_object_per_offset_and_a_copy_starts_fresh() {
let t = ClassTemplateId(1013);
let a = alloc(t, 2);
let b = alloc(t, 2);
assert_eq!(randc_state(a, 0), Some(RandcState::default()));
let st = RandcState {
live: true,
fingerprint: 7,
remaining: vec![3, 1],
};
assert!(set_randc_states(a, vec![(1, st.clone())]));
assert_eq!(randc_state(a, 1), Some(st));
assert_eq!(randc_state(a, 0), Some(RandcState::default()));
assert_eq!(randc_state(b, 1), Some(RandcState::default()), "per object");
let c = copy(a).unwrap();
assert_eq!(randc_state(c, 1), Some(RandcState::default()), "fresh copy");
release(a);
assert_eq!(randc_state(a, 1), None);
assert!(!set_randc_states(a, vec![(1, RandcState::default())]));
release(b);
release(c);
}
}