use std::{any::Any, cell::UnsafeCell, sync::Arc};
struct MemoryAllocation {
words: Box<[UnsafeCell<u64>]>,
start_word: usize,
capacity_words: usize,
}
unsafe impl Sync for MemoryAllocation {}
impl MemoryAllocation {
fn zeroed(capacity_words: usize) -> Self {
let alignment = celox_state_layout::LANE_SEGMENT_ALIGN;
let extra_words = alignment / std::mem::size_of::<u64>();
let allocation_words = capacity_words
.checked_add(extra_words)
.expect("simulation memory allocation size overflow");
let words = std::iter::repeat_with(|| UnsafeCell::new(0u64))
.take(allocation_words)
.collect::<Vec<_>>()
.into_boxed_slice();
let start_word = words.as_ptr().align_offset(alignment);
assert!(start_word < extra_words);
Self {
words,
start_word,
capacity_words,
}
}
fn as_ptr(&self) -> *const u64 {
unsafe { self.words.as_ptr().add(self.start_word).cast::<u64>() }
}
fn as_mut_ptr(&self) -> *mut u64 {
unsafe { UnsafeCell::raw_get(self.words.as_ptr().add(self.start_word)) }
}
fn capacity_words(&self) -> usize {
self.capacity_words
}
}
pub(crate) struct MemoryImage {
allocation: Arc<MemoryAllocation>,
logical_words: usize,
raw_view_exposed: bool,
}
impl MemoryImage {
pub(crate) fn zeroed(logical_words: usize) -> Self {
Self {
allocation: Arc::new(MemoryAllocation::zeroed(logical_words)),
logical_words,
raw_view_exposed: false,
}
}
pub(crate) fn as_ptr(&self) -> *const u64 {
self.allocation.as_ptr()
}
pub(crate) fn as_mut_ptr(&mut self) -> *mut u64 {
self.allocation.as_mut_ptr()
}
pub(crate) fn expose_mut_ptr(&mut self) -> *mut u64 {
self.raw_view_exposed = true;
self.as_mut_ptr()
}
pub(crate) fn write_bytes(&mut self, offset: usize, bytes: &[u8]) {
assert!(
offset + bytes.len() <= self.logical_words * 8,
"write past the memory image"
);
unsafe {
std::ptr::copy_nonoverlapping(
bytes.as_ptr(),
self.as_mut_ptr().cast::<u8>().add(offset),
bytes.len(),
);
}
}
pub(crate) fn write_header_word(&mut self, offset: usize, value: u64) {
self.write_bytes(offset, &value.to_ne_bytes());
}
pub(crate) fn vcd_tracking_enabled(&self) -> bool {
!self.raw_view_exposed
}
#[cfg(test)]
fn as_slice(&self) -> &[u64] {
unsafe { std::slice::from_raw_parts(self.as_ptr(), self.logical_words) }
}
pub(crate) fn as_mut_slice(&mut self) -> &mut [u64] {
unsafe { std::slice::from_raw_parts_mut(self.as_mut_ptr(), self.logical_words) }
}
pub(crate) fn len_words(&self) -> usize {
self.logical_words
}
pub(crate) fn capacity_words(&self) -> usize {
self.allocation.capacity_words()
}
pub(crate) fn reserve_total(&mut self, total_words: usize) {
if total_words <= self.capacity_words() {
return;
}
assert_eq!(
Arc::strong_count(&self.allocation),
1,
"cannot move a simulation memory image after exposing an owner"
);
let replacement = Arc::new(MemoryAllocation::zeroed(total_words));
unsafe {
std::ptr::copy_nonoverlapping(
self.allocation.as_ptr(),
replacement.as_mut_ptr(),
self.logical_words,
);
}
self.allocation = replacement;
}
pub(crate) fn resize_zeroed_within_capacity(&mut self, new_words: usize) {
assert!(
new_words <= self.capacity_words(),
"simulation memory image exceeded its reserved capacity"
);
if new_words > self.logical_words {
unsafe {
std::ptr::write_bytes(
self.allocation.as_mut_ptr().add(self.logical_words),
0,
new_words - self.logical_words,
);
}
}
self.logical_words = new_words;
}
pub(crate) fn owner(&self) -> Arc<dyn Any + Send + Sync> {
self.allocation.clone()
}
}
impl Default for MemoryImage {
fn default() -> Self {
Self::zeroed(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocation_and_reserved_growth_preserve_coherence_alignment() {
for size in [0, 1, 2, 31, 32, 33, 129, 1025] {
let mut image = MemoryImage::zeroed(size);
assert_eq!(
image.as_ptr() as usize % celox_state_layout::LANE_SEGMENT_ALIGN,
0
);
assert_eq!(image.capacity_words(), size);
image.as_mut_slice().fill(17);
image.reserve_total(size + 33);
assert_eq!(
image.as_ptr() as usize % celox_state_layout::LANE_SEGMENT_ALIGN,
0
);
assert_eq!(image.as_slice(), vec![17; size]);
let pointer = image.as_ptr();
let owner = image.owner();
image.resize_zeroed_within_capacity(size + 33);
assert_eq!(image.as_ptr(), pointer);
assert!(image.as_slice()[size..].iter().all(|word| *word == 0));
drop(image);
assert_eq!(unsafe { *pointer.add(size) }, 0);
drop(owner);
}
}
#[test]
fn owner_keeps_only_the_allocation_alive() {
let image = MemoryImage::zeroed(2);
let allocation = Arc::downgrade(&image.allocation);
let first = image.owner();
let second = image.owner();
drop(image);
assert!(allocation.upgrade().is_some());
drop(first);
assert!(allocation.upgrade().is_some());
drop(second);
assert!(allocation.upgrade().is_none());
}
#[test]
fn reserved_growth_preserves_pointer_and_zeroes_extension() {
let mut image = MemoryImage::zeroed(2);
image.as_mut_slice().copy_from_slice(&[11, 22]);
image.reserve_total(4);
let ptr = image.as_ptr();
image.resize_zeroed_within_capacity(4);
assert_eq!(image.as_ptr(), ptr);
assert_eq!(image.as_slice(), &[11, 22, 0, 0]);
}
#[test]
#[should_panic(expected = "after exposing an owner")]
fn reserve_rejects_moving_an_exposed_allocation() {
let mut image = MemoryImage::zeroed(1);
let _owner = image.owner();
image.reserve_total(2);
}
#[test]
fn empty_image_has_no_logical_words() {
let mut image = MemoryImage::default();
assert!(image.as_slice().is_empty());
assert!(image.as_mut_slice().is_empty());
}
}