use std::cell::{Cell, UnsafeCell};
use std::ptr;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BuildMode {
FourState,
TwoState,
}
pub struct Arena {
mode: BuildMode,
inline: UnsafeCell<Vec<u128>>,
wide_aval: UnsafeCell<Vec<u64>>,
wide_bval: UnsafeCell<Vec<u64>>,
inline_2s: UnsafeCell<Vec<u64>>,
wide_2s: UnsafeCell<Vec<u64>>,
force_wide_aval: UnsafeCell<Vec<u64>>,
force_wide_bval: UnsafeCell<Vec<u64>>,
wide_sealed: Cell<bool>,
pub(crate) huge: super::huge_mirror::HugeMirrors,
}
impl std::fmt::Debug for Arena {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (inline_len, wide_len, inline_2s_len, wide_2s_len, force_wide_len) = unsafe {
(
(*self.inline.get()).len(),
(*self.wide_aval.get()).len(),
(*self.inline_2s.get()).len(),
(*self.wide_2s.get()).len(),
(*self.force_wide_aval.get()).len(),
)
};
f.debug_struct("Arena")
.field("mode", &self.mode)
.field("inline_len", &inline_len)
.field("wide_len", &wide_len)
.field("inline_2s_len", &inline_2s_len)
.field("wide_2s_len", &wide_2s_len)
.field("force_wide_len", &force_wide_len)
.finish()
}
}
impl Arena {
pub fn new(mode: BuildMode) -> Self {
Arena {
mode,
inline: UnsafeCell::new(Vec::new()),
wide_aval: UnsafeCell::new(Vec::new()),
wide_bval: UnsafeCell::new(Vec::new()),
inline_2s: UnsafeCell::new(Vec::new()),
wide_2s: UnsafeCell::new(Vec::new()),
force_wide_aval: UnsafeCell::new(Vec::new()),
force_wide_bval: UnsafeCell::new(Vec::new()),
wide_sealed: Cell::new(false),
huge: super::huge_mirror::HugeMirrors::default(),
}
}
pub(crate) fn seal_wide_plane(&self) {
self.wide_sealed.set(true);
}
#[cfg(test)]
pub(crate) fn is_wide_plane_sealed(&self) -> bool {
self.wide_sealed.get()
}
pub unsafe fn alloc_inline_words(&self, n: u32) -> u32 {
unsafe {
let inline = &mut *self.inline.get();
let inline_2s = &mut *self.inline_2s.get();
let off = inline.len() as u32;
for _ in 0..n {
inline.push(0);
inline_2s.push(0);
}
off
}
}
pub unsafe fn alloc_inline_word(&self) -> u32 {
unsafe { self.alloc_inline_words(1) }
}
pub unsafe fn alloc_wide_words(&self, words: u32) -> u32 {
assert!(
words == 0 || !self.wide_sealed.get(),
"Arena::alloc_wide_words: refusing to grow the wide plane -- \
sv_get_wide has already handed out a WideView aliasing this \
Arena's wide_aval/wide_bval live storage, and growing them now \
would reallocate that storage and dangle it (a use-after-free); \
no further wide-word allocation (e.g. via allocate_instance) is \
legal against this Arena once any sv_get_wide call has been made"
);
unsafe {
let wide_aval = &mut *self.wide_aval.get();
let wide_bval = &mut *self.wide_bval.get();
let wide_2s = &mut *self.wide_2s.get();
let off = wide_aval.len() as u32;
for _ in 0..words {
wide_aval.push(0);
wide_bval.push(0);
wide_2s.push(0);
}
off
}
}
pub unsafe fn alloc_force_wide_words(&self, words: u32) -> u32 {
unsafe {
let force_wide_aval = &mut *self.force_wide_aval.get();
let force_wide_bval = &mut *self.force_wide_bval.get();
let off = force_wide_aval.len() as u32;
for _ in 0..words {
force_wide_aval.push(0);
force_wide_bval.push(0);
}
off
}
}
pub fn force_wide_aval(&self) -> &[u64] {
unsafe { &*self.force_wide_aval.get() }
}
pub fn force_wide_bval(&self) -> &[u64] {
unsafe { &*self.force_wide_bval.get() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn force_wide_aval_mut(&self) -> &mut [u64] {
unsafe { &mut *self.force_wide_aval.get() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn force_wide_bval_mut(&self) -> &mut [u64] {
unsafe { &mut *self.force_wide_bval.get() }
}
pub unsafe fn alloc_inline_2s_words(&self, n: u32) -> u32 {
unsafe { self.alloc_inline_words(n) }
}
pub unsafe fn alloc_wide_2s_words(&self, n: u32) -> u32 {
unsafe { self.alloc_wide_words(n) }
}
pub fn inline(&self) -> &[u128] {
unsafe { &*self.inline.get() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn inline_mut(&self) -> &mut [u128] {
unsafe { &mut *self.inline.get() }
}
pub fn wide_aval(&self) -> &[u64] {
unsafe { &*self.wide_aval.get() }
}
pub fn wide_bval(&self) -> &[u64] {
unsafe { &*self.wide_bval.get() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn wide_aval_mut(&self) -> &mut [u64] {
unsafe { &mut *self.wide_aval.get() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn wide_bval_mut(&self) -> &mut [u64] {
unsafe { &mut *self.wide_bval.get() }
}
pub(crate) unsafe fn wide_aval_ptr(&self) -> *mut u64 {
unsafe { (*self.wide_aval.get()).as_mut_ptr() }
}
pub(crate) unsafe fn wide_bval_ptr(&self) -> *mut u64 {
unsafe { (*self.wide_bval.get()).as_mut_ptr() }
}
pub fn inline_2s(&self) -> &[u64] {
unsafe { &*self.inline_2s.get() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn inline_2s_mut(&self) -> &mut [u64] {
unsafe { &mut *self.inline_2s.get() }
}
pub fn wide_2s(&self) -> &[u64] {
unsafe { &*self.wide_2s.get() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn wide_2s_mut(&self) -> &mut [u64] {
unsafe { &mut *self.wide_2s.get() }
}
pub fn mode(&self) -> BuildMode {
self.mode
}
pub fn ffi_view(&mut self) -> StateArena {
StateArena {
inline: self.inline.get_mut().as_mut_ptr(),
wide_aval: if self.mode == BuildMode::TwoState {
ptr::null_mut()
} else {
self.wide_aval.get_mut().as_mut_ptr()
},
wide_bval: if self.mode == BuildMode::TwoState {
ptr::null_mut()
} else {
self.wide_bval.get_mut().as_mut_ptr()
},
}
}
pub unsafe fn raw_inline_ptr(&mut self) -> *mut u128 {
self.inline.get_mut().as_mut_ptr()
}
}
#[repr(C)]
pub struct StateArena {
pub inline: *mut u128,
pub wide_aval: *mut u64,
pub wide_bval: *mut u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_inline_word_is_contiguous_no_per_signal_heap_object() {
let arena = Arena::new(BuildMode::FourState);
let offsets: Vec<u32> = (0..5)
.map(|_| unsafe { arena.alloc_inline_word() })
.collect();
assert_eq!(offsets, vec![0, 1, 2, 3, 4]);
}
#[cfg(feature = "frontend")] #[test]
fn inline_word_low64_high64_matches_bits_to_four_state() {
use super::super::{FourStateValue, bits_to_four_state};
use sv_lang::Bit;
let fv = bits_to_four_state(&[Bit::One, Bit::X, Bit::Zero, Bit::Z]);
let (expected_val, expected_unk) = match fv {
FourStateValue::Narrow(w) => (w as u64, (w >> 64) as u64),
FourStateValue::Wide { .. } => panic!("expected Narrow for 4 bits"),
};
let arena = Arena::new(BuildMode::FourState);
let off = unsafe { arena.alloc_inline_word() };
unsafe {
arena.inline_mut()[off as usize] =
(expected_val as u128) | ((expected_unk as u128) << 64);
}
let word = arena.inline()[off as usize];
let (val, unk) = (word as u64, (word >> 64) as u64);
assert_eq!(val, expected_val);
assert_eq!(unk, expected_unk);
}
#[test]
fn wide_planes_share_identical_relative_offsets() {
let arena = Arena::new(BuildMode::FourState);
let o1 = unsafe { arena.alloc_wide_words(2) };
unsafe {
arena.wide_aval_mut()[o1 as usize] = 0xAAAA_AAAA_AAAA_AAAA;
arena.wide_bval_mut()[o1 as usize] = 0xBBBB_BBBB_BBBB_BBBB;
arena.wide_aval_mut()[(o1 + 1) as usize] = 0xCCCC_CCCC_CCCC_CCCC;
arena.wide_bval_mut()[(o1 + 1) as usize] = 0xDDDD_DDDD_DDDD_DDDD;
}
let o2 = unsafe { arena.alloc_wide_words(2) };
assert_eq!(o2, o1 + 2);
assert_eq!(arena.wide_aval()[o1 as usize], 0xAAAA_AAAA_AAAA_AAAA);
assert_eq!(arena.wide_bval()[o1 as usize], 0xBBBB_BBBB_BBBB_BBBB);
assert_eq!(arena.wide_aval()[(o1 + 1) as usize], 0xCCCC_CCCC_CCCC_CCCC);
assert_eq!(arena.wide_bval()[(o1 + 1) as usize], 0xDDDD_DDDD_DDDD_DDDD);
assert_eq!(arena.wide_aval()[o2 as usize], 0);
assert_eq!(arena.wide_bval()[o2 as usize], 0);
}
#[test]
fn no_two_signals_ever_share_a_64bit_word_even_two_1bit_wires() {
let arena = Arena::new(BuildMode::FourState);
let a = unsafe { arena.alloc_inline_word() };
let b = unsafe { arena.alloc_inline_word() };
assert_eq!(b - a, 1);
assert_ne!(a, b);
}
#[test]
fn concurrent_writes_to_disjoint_words_produce_no_torn_reads() {
const N: usize = 8;
let mut arena = Arena::new(BuildMode::FourState);
for _ in 0..N {
unsafe {
arena.alloc_inline_word();
}
}
let patterns: [u128; N] = [
0x1111_1111_1111_1111_1111_1111_1111_1111,
0x2222_2222_2222_2222_2222_2222_2222_2222,
0x3333_3333_3333_3333_3333_3333_3333_3333,
0x4444_4444_4444_4444_4444_4444_4444_4444,
0x5555_5555_5555_5555_5555_5555_5555_5555,
0x6666_6666_6666_6666_6666_6666_6666_6666,
0x7777_7777_7777_7777_7777_7777_7777_7777,
0x8888_8888_8888_8888_8888_8888_8888_8888,
];
struct SendPtr(*mut u128);
unsafe impl Send for SendPtr {}
let base = unsafe { arena.raw_inline_ptr() };
std::thread::scope(|s| {
for (i, &pattern) in patterns.iter().enumerate() {
let p = SendPtr(unsafe { base.add(i) });
s.spawn(move || {
let p = p;
unsafe { ptr::write(p.0, pattern) };
});
}
});
for (i, &pattern) in patterns.iter().enumerate() {
assert_eq!(arena.inline()[i], pattern, "torn/wrong write at word {i}");
}
}
#[test]
fn alloc_inline_words_reserves_n_contiguous_words_in_one_call() {
let arena = Arena::new(BuildMode::FourState);
let single = unsafe { arena.alloc_inline_word() };
assert_eq!(single, 0);
let bulk_start = unsafe { arena.alloc_inline_words(3) };
assert_eq!(
bulk_start, 1,
"must start immediately after the single word, no gap"
);
assert_eq!(arena.inline().len(), 4, "1 single word + 3 bulk words");
let next = unsafe { arena.alloc_inline_word() };
assert_eq!(
next, 4,
"a later single-word alloc must not overlap the bulk region"
);
}
#[test]
fn alloc_inline_words_of_zero_is_a_no_op_returning_current_length() {
let arena = Arena::new(BuildMode::FourState);
unsafe {
arena.alloc_inline_word();
}
let off = unsafe { arena.alloc_inline_words(0) };
assert_eq!(off, 1);
assert_eq!(
arena.inline().len(),
1,
"zero-word request must not grow the arena"
);
let next = unsafe { arena.alloc_inline_word() };
assert_eq!(
next, 1,
"the zero-word request must not have consumed an offset"
);
}
#[test]
fn ffi_view_wide_planes_null_only_in_two_state_mode() {
let mut two_state = Arena::new(BuildMode::TwoState);
unsafe {
two_state.alloc_wide_words(1);
}
let view = two_state.ffi_view();
assert!(view.wide_aval.is_null());
assert!(view.wide_bval.is_null());
let mut four_state = Arena::new(BuildMode::FourState);
unsafe {
four_state.alloc_wide_words(1);
}
let view = four_state.ffi_view();
assert!(!view.wide_aval.is_null());
assert!(!view.wide_bval.is_null());
}
#[test]
fn alloc_wide_words_is_unaffected_before_the_plane_is_sealed() {
let arena = Arena::new(BuildMode::FourState);
assert!(!arena.is_wide_plane_sealed());
let a = unsafe { arena.alloc_wide_words(3) };
let b = unsafe { arena.alloc_wide_words(5) };
assert_eq!(a, 0);
assert_eq!(b, 3);
}
#[test]
#[should_panic(expected = "refusing to grow the wide plane")]
fn alloc_wide_words_panics_on_nonzero_growth_once_sealed() {
let arena = Arena::new(BuildMode::FourState);
unsafe {
arena.alloc_wide_words(2);
}
arena.seal_wide_plane();
assert!(arena.is_wide_plane_sealed());
unsafe {
arena.alloc_wide_words(1);
}
}
#[test]
fn alloc_wide_words_of_zero_is_still_a_no_op_once_sealed() {
let arena = Arena::new(BuildMode::FourState);
let off = unsafe { arena.alloc_wide_words(4) };
arena.seal_wide_plane();
let after_seal = unsafe { arena.alloc_wide_words(0) };
assert_eq!(
after_seal,
off + 4,
"zero-word request must still return the current length"
);
assert_eq!(
arena.wide_aval().len(),
4,
"zero-word request must not grow the arena even when sealed"
);
}
}