use crate::{Memory, DropFn};
use crate::droplist::{DropList, DropListWriteResult, DropItem};
use std::ptr::{null_mut};
use crate::block::{Block, PlacementError};
use std::fmt::Debug;
#[derive(Debug)]
pub enum UploadError {
DropListDoesNotFit,
ItemDoesNotFit,
MetadataDoesNotFit,
ArenaIsNotAlive,
}
impl std::fmt::Display for UploadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UploadError::DropListDoesNotFit => std::fmt::Display::fmt("Drop list does not fit in a block", f),
UploadError::ItemDoesNotFit => std::fmt::Display::fmt("Item is bigger than a block", f),
UploadError::MetadataDoesNotFit => std::fmt::Display::fmt("Metadata does not fit in a first arena block", f),
UploadError::ArenaIsNotAlive => std::fmt::Display::fmt("Arena is not alive", f),
}
}
}
impl std::error::Error for UploadError {}
struct ArenaMetadata {
memory: Memory,
last_block: Option<Block>,
first_drop_list: *mut DropList,
last_drop_list: *mut DropList,
strong_rc: i64,
rc: i64,
}
impl ArenaMetadata {
#[inline(always)]
pub fn inc_rc(&mut self) {
self.strong_rc += 1;
self.rc += 1;
trace!("inc_rc s {} t {}", self.strong_rc, self.rc);
}
#[inline(always)]
pub fn dec_rc(&mut self) {
self.strong_rc -= 1;
self.rc -= 1;
trace!("dec_rc s {} t {}", self.strong_rc, self.rc);
}
#[inline(always)]
pub fn inc_weak(&mut self) {
self.rc += 1;
trace!("inc_wk s {} t {}", self.strong_rc, self.rc);
}
#[inline(always)]
pub fn dec_weak(&mut self) {
self.rc -= 1;
trace!("dec_wk s {} t {}", self.strong_rc, self.rc);
}
unsafe fn push_drop_fn<T>(&mut self, data: *const u8) -> Result<*const Option<DropItem>, UploadError> {
debug_assert_ne!(null_mut(), self.first_drop_list, "push: drop list not null (1)");
debug_assert_ne!(null_mut(), self.last_drop_list, "push: drop list not null (2)");
Ok(match (*self.last_drop_list).push_drop_fn::<T>(data) {
(DropListWriteResult::ListFull, item) => {
self.push_next_drop_list()?;
item
},
(DropListWriteResult::ListNotFull, item) => item,
})
}
pub unsafe fn push_custom_drop_fn(&mut self, fun: DropFn, data: *const u8) -> Result<*const Option<DropItem>, UploadError> {
debug_assert_ne!(null_mut(), self.first_drop_list, "push: drop list not null (3)");
debug_assert_ne!(null_mut(), self.last_drop_list, "push: drop list not null (4)");
Ok(match (*self.last_drop_list).push_custom_drop_fn(fun, data) {
(DropListWriteResult::ListFull, item) => {
self.push_next_drop_list()?;
item
},
(DropListWriteResult::ListNotFull, item) => item,
})
}
unsafe fn push_next_drop_list(&mut self) -> Result<(), UploadError> {
let next_drop_list = match self.upload_no_drop(DropList::empty()) {
Ok(v) => v,
Err(_) => return Err(UploadError::DropListDoesNotFit),
};
debug_assert_ne!(self.last_drop_list, null_mut(), "last drop list not null");
(*self.last_drop_list).set_next_list(next_drop_list);
self.last_drop_list = next_drop_list;
Ok(())
}
pub unsafe fn upload_auto_drop<T>(&mut self, value: T) -> Result<(*mut T, *const Option<DropItem>), UploadError> {
let last_block = self.last_block.as_mut().unwrap();
match last_block.push_copy::<T>(&value) {
Ok(value_ptr) => {
std::mem::forget(value);
let drop_item = self.push_drop_fn::<T>(value_ptr as *const u8)?;
return Ok((value_ptr, drop_item));
},
Err(e) => match e {
PlacementError::NotEnoughSpaceInBlock => (),
PlacementError::ItemTooBig => return Err(UploadError::ItemDoesNotFit),
}
}
let mut block = Some(Block::new(self.memory.take_block()));
std::mem::swap(&mut block, &mut self.last_block);
let last_block = self.last_block.as_mut().unwrap();
last_block.set_previous_block(block.unwrap());
let value_ptr = last_block.push_copy::<T>(&value).ok().expect("fits into subsequent block (1)");
std::mem::forget(value);
let drop_item = self.push_drop_fn::<T>(value_ptr as *const u8)?;
Ok((value_ptr, drop_item))
}
pub unsafe fn upload_no_drop<T>(&mut self, value: T) -> Result<*mut T, UploadError> {
let last_block = self.last_block.as_mut().unwrap();
match last_block.push_copy::<T>(&value) {
Ok(value_ptr) => {
std::mem::forget(value);
return Ok(value_ptr);
},
Err(e) => match e {
PlacementError::NotEnoughSpaceInBlock => (),
PlacementError::ItemTooBig => return Err(UploadError::ItemDoesNotFit),
}
}
let mut block = Some(Block::new(self.memory.take_block()));
std::mem::swap(&mut block, &mut self.last_block);
let last_block = self.last_block.as_mut().unwrap();
last_block.set_previous_block(block.unwrap());
let value_ptr = last_block.push_copy::<T>(&value).ok().expect("fits into subsequent block (2)");
std::mem::forget(value);
Ok(value_ptr)
}
pub unsafe fn upload_no_drop_bytes(&mut self, len: usize, value: impl Iterator<Item=u8>) -> Result<*mut u8, UploadError> {
let last_block = self.last_block.as_mut().unwrap();
let (remaining_bytes_for_alignment, aligned_start) = last_block.remaining_bytes_for_alignment::<[u8; 1]>();
if remaining_bytes_for_alignment >= len as isize {
return Ok(last_block.upload_bytes_unchecked(aligned_start, len, value));
}
if len > last_block.largest_item_size() {
return Err(UploadError::ItemDoesNotFit);
}
let mut block = Some(Block::new(self.memory.take_block()));
std::mem::swap(&mut block, &mut self.last_block);
let last_block = self.last_block.as_mut().unwrap();
last_block.set_previous_block(block.unwrap());
let (remaining_bytes_for_alignment, aligned_start) = last_block.remaining_bytes_for_alignment::<[u8; 1]>();
if remaining_bytes_for_alignment >= len as isize {
return Ok(last_block.upload_bytes_unchecked(aligned_start, len, value));
}
unreachable!("upload_no_drop_bytes failed after acquiring the next block")
}
pub unsafe fn alloc_no_drop_items_aligned_uninit<T>(&mut self, len: usize, offset_between_items: usize) -> Result<*mut T, UploadError> {
let last_block = self.last_block.as_mut().unwrap();
let (remaining_bytes_for_alignment, aligned_start) = last_block.remaining_bytes_for_alignment::<T>();
let total_array_len = len * offset_between_items;
if remaining_bytes_for_alignment >= total_array_len as isize {
return Ok(last_block.upload_bytes_unchecked_uninit(aligned_start, total_array_len) as *mut T);
}
if total_array_len > last_block.largest_item_size() {
return Err(UploadError::ItemDoesNotFit);
}
let mut block = Some(Block::new(self.memory.take_block()));
std::mem::swap(&mut block, &mut self.last_block);
let last_block = self.last_block.as_mut().unwrap();
last_block.set_previous_block(block.unwrap());
let (remaining_bytes_for_alignment, aligned_start) = last_block.remaining_bytes_for_alignment::<T>();
if remaining_bytes_for_alignment >= total_array_len as isize {
return Ok(last_block.upload_bytes_unchecked_uninit(aligned_start, total_array_len) as *mut T);
}
unreachable!("alloc_no_drop_items_aligned_uninit failed after acquiring the next block")
}
pub unsafe fn drop_objects(&mut self) {
debug_assert_ne!(null_mut(), self.first_drop_list, "drop_objects: drop list not null");
(*self.first_drop_list).execute_drop_chain();
self.first_drop_list = null_mut();
self.last_drop_list = null_mut();
}
pub unsafe fn reclaim_memory(&mut self) {
let mut block = None;
std::mem::swap(&mut block, &mut self.last_block);
while block.is_some() {
let (previous_block, data) = block.unwrap().into_previous_block_and_data();
self.memory.return_block(data);
block = previous_block;
}
}
}
pub struct WeakArena {
metadata: *mut ArenaMetadata,
}
pub struct Arena {
metadata: *mut ArenaMetadata,
}
impl Arena {
pub fn new(memory: &Memory) -> Result<Arena, UploadError> {
let mut memory = memory.clone();
let mut block = Block::new(memory.take_block());
let drop_list = unsafe { block.push(DropList::empty()) }.map_err(|_| UploadError::DropListDoesNotFit)?;
let metadata = unsafe { block.push(ArenaMetadata {
memory,
last_block: None,
first_drop_list: drop_list,
last_drop_list: drop_list,
strong_rc: 1,
rc: 1
}) }.map_err(|_| UploadError::MetadataDoesNotFit)?;
unsafe { (*metadata).last_block = Some(block) };
Ok(Arena {
metadata,
})
}
#[inline(always)]
unsafe fn md(&self) -> &mut ArenaMetadata {
std::mem::transmute::<*mut ArenaMetadata, &mut ArenaMetadata>(self.metadata)
}
#[inline(always)]
pub unsafe fn upload_auto_drop<T>(&self, value: T) -> Result<(*mut T, *const Option<DropItem>), UploadError> {
self.md().upload_auto_drop::<T>(value)
}
#[inline(always)]
pub unsafe fn upload_no_drop<T>(&self, value: T) -> Result<*mut T, UploadError> {
self.md().upload_no_drop::<T>(value)
}
#[inline(always)]
pub unsafe fn upload_no_drop_bytes(&self, len: usize, value: impl Iterator<Item=u8>) -> Result<*mut u8, UploadError> {
self.md().upload_no_drop_bytes(len, value)
}
#[inline(always)]
pub unsafe fn alloc_no_drop_items_aligned_uninit<T>(&self, len: usize, offset_between_items: usize) -> Result<*mut T, UploadError> {
self.md().alloc_no_drop_items_aligned_uninit::<T>(len, offset_between_items)
}
#[inline(always)]
pub unsafe fn push_custom_drop_fn(&self, fun: DropFn, data: *const u8) -> Result<*const Option<DropItem>, UploadError> {
self.md().push_custom_drop_fn(fun, data)
}
pub fn to_weak_arena(&self) -> WeakArena {
trace!("split weak arena");
unsafe { self.md().inc_weak() };
WeakArena {
metadata: self.metadata,
}
}
}
impl WeakArena {
#[inline(always)]
pub fn is_alive(&self) -> bool {
unsafe { self.md().strong_rc > 0 }
}
#[inline(always)]
unsafe fn md(&self) -> &mut ArenaMetadata {
std::mem::transmute::<*mut ArenaMetadata, &mut ArenaMetadata>(self.metadata)
}
#[inline(always)]
pub unsafe fn upload_auto_drop<T>(&self, value: T) -> Result<(*mut T, *const Option<DropItem>), UploadError> {
if self.is_alive() {
Ok(self.md().upload_auto_drop::<T>(value)?)
} else {
Err(UploadError::ArenaIsNotAlive)
}
}
#[inline(always)]
pub unsafe fn upload_no_drop<T>(&self, value: T) -> Result<*mut T, UploadError> {
if self.is_alive() {
Ok(self.md().upload_no_drop::<T>(value)?)
} else {
Err(UploadError::ArenaIsNotAlive)
}
}
#[inline(always)]
pub unsafe fn upload_no_drop_bytes(&self, len: usize, value: impl Iterator<Item=u8>) -> Result<*mut u8, UploadError> {
if self.is_alive() {
Ok(self.md().upload_no_drop_bytes(len, value)?)
} else {
Err(UploadError::ArenaIsNotAlive)
}
}
pub fn arena(&self) -> Option<Arena> {
if self.is_alive() {
trace!("upgrade weak to strong arena");
unsafe { self.md().inc_rc() };
Some(Arena {
metadata: self.metadata,
})
} else {
None
}
}
}
impl Clone for Arena {
fn clone(&self) -> Self {
trace!("clone arena");
let metadata = self.metadata;
unsafe { (*metadata).inc_rc(); }
Arena {
metadata,
}
}
}
impl Clone for WeakArena {
fn clone(&self) -> Self {
trace!("clone weak");
let metadata = self.metadata;
unsafe { (*metadata).inc_weak(); }
WeakArena {
metadata,
}
}
}
impl Drop for Arena {
fn drop(&mut self) {
trace!("drop arena");
let metadata = unsafe { self.md() };
(*metadata).dec_rc();
if (*metadata).strong_rc == 0 {
trace!("drop arena objects");
unsafe { (*metadata).drop_objects() };
}
if (*metadata).rc == 0 {
trace!("reclaim memory");
unsafe { metadata.reclaim_memory() };
}
}
}
impl Drop for WeakArena {
fn drop(&mut self) {
trace!("drop weak arena");
let metadata = unsafe { self.md() };
(*metadata).dec_weak();
if (*metadata).rc == 0 {
trace!("reclaim memory");
unsafe { metadata.reclaim_memory() };
}
}
}
#[cfg(test)]
mod arena_tests {
use crate::{Memory, Arena, N};
use crate::dropflag::DropFlag;
use std::cell::RefCell;
#[derive(Debug)]
struct Compact {
value: DropFlag<i32>,
}
impl PartialEq for Compact {
fn eq(&self, other: &Self) -> bool {
((*self.value).borrow()).eq(&(*other.value).borrow())
}
}
impl Eq for Compact {}
impl Drop for Compact {
fn drop(&mut self) {
*self.value.borrow_mut() -= 1;
}
}
#[allow(dead_code)]
struct Nested<T> {
dropflag: DropFlag<i32>,
inner: T,
}
impl<T> Drop for Nested<T> {
fn drop(&mut self) {
*self.dropflag.borrow_mut() -= 1;
}
}
#[test]
fn value_can_not_be_used_when_arena_goes_out_of_scope() {
let flag = DropFlag::new(RefCell::new(1));
let mut obj = {
let mem = Memory::new();
let arena = Arena::new(&mem).unwrap();
let mut obj = N::new(&arena, Compact { value: flag.clone() }).unwrap();
assert_eq!(1, *(*flag).borrow(), "drop was not called");
assert_ne!(None, obj.val(), "value can be accessed");
assert_ne!(None, obj.var(), "value can be accessed");
obj
};
assert_eq!(0, *(*flag).borrow(), "drop was called");
assert_eq!(None, obj.val(), "value can not be accessed");
assert_eq!(None, obj.var(), "value can not be accessed");
}
#[test]
fn nested_objects_are_dropped_properly() {
let f1 = DropFlag::new(RefCell::new(1));
let f2 = DropFlag::new(RefCell::new(1));
let mem = Memory::new();
let _obj = {
let arena = Arena::new(&mem).unwrap();
let obj = N::new(&arena,
Nested {
dropflag: f1.clone(),
inner: N::new(&arena,
Nested {
dropflag: f2.clone(),
inner: ()
}
).unwrap()
}
).unwrap();
assert_eq!(1, *(*f1).borrow(), "drop was not called");
assert_eq!(1, *(*f2).borrow(), "drop was not called");
obj
};
assert_eq!(0, *(*f1).borrow(), "drop was called");
assert_eq!(0, *(*f2).borrow(), "drop was called");
}
}