use core::alloc::{Layout, LayoutError};
use core::cell::Cell;
use core::fmt;
use core::mem::{align_of, needs_drop, replace, size_of, take};
use core::ptr::{self, addr_of, addr_of_mut, NonNull};
use rust_alloc::sync::Arc;
use crate::alloc;
use crate::alloc::alloc::{Allocator, Global};
use crate::alloc::fmt::TryWrite;
use crate::hash::Hash;
use crate::runtime::{
Access, AccessError, BorrowMut, BorrowRef, Formatter, IntoOutput, ProtocolCaller, Rtti,
RttiKind, RuntimeError, Snapshot, TypeInfo, Value, VmResult,
};
#[derive(Debug)]
pub(crate) enum DynamicTakeError {
Access(AccessError),
Alloc(alloc::Error),
}
impl fmt::Display for DynamicTakeError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DynamicTakeError::Access(error) => error.fmt(f),
DynamicTakeError::Alloc(error) => error.fmt(f),
}
}
}
impl core::error::Error for DynamicTakeError {}
impl From<AccessError> for DynamicTakeError {
fn from(error: AccessError) -> Self {
Self::Access(error)
}
}
impl From<alloc::Error> for DynamicTakeError {
fn from(error: alloc::Error) -> Self {
Self::Alloc(error)
}
}
pub struct Dynamic<H, T> {
shared: NonNull<Shared<H, T>>,
}
impl<H, T> Dynamic<H, T> {
pub(crate) fn new(
rtti: H,
it: impl IntoIterator<Item = T, IntoIter: ExactSizeIterator>,
) -> alloc::Result<Self> {
let it = it.into_iter();
let this = Self::alloc(rtti, it.len())?;
unsafe {
let data = Shared::as_data_ptr(this.shared);
for (i, value) in it.enumerate() {
data.add(i).write(value);
}
}
Ok(this)
}
fn alloc(rtti: H, len: usize) -> alloc::Result<Self> {
let layout = Shared::<H, T>::layout(len)?;
let shared = Global.allocate(layout)?.cast::<Shared<H, T>>();
unsafe {
shared.write(Shared {
rtti,
count: Cell::new(1),
access: Access::new(),
len,
data: [],
});
}
Ok(Self { shared })
}
#[inline]
pub(crate) fn is_readable(&self) -> bool {
unsafe { self.shared.as_ref().access.is_shared() }
}
#[inline]
pub(crate) fn is_writable(&self) -> bool {
unsafe { self.shared.as_ref().access.is_exclusive() }
}
#[inline]
pub(crate) fn snapshot(&self) -> Snapshot {
unsafe { self.shared.as_ref().access.snapshot() }
}
#[inline]
pub(crate) fn len(&self) -> usize {
unsafe { self.shared.as_ref().len }
}
#[inline]
pub(crate) fn rtti(&self) -> &H {
unsafe { &self.shared.as_ref().rtti }
}
#[inline]
pub(crate) fn borrow_ref(&self) -> Result<BorrowRef<[T]>, AccessError> {
unsafe {
let guard = self.shared.as_ref().access.shared()?;
let data = Shared::as_data_ptr(self.shared);
let data = NonNull::slice_from_raw_parts(data, self.shared.as_ref().len);
Ok(BorrowRef::new(data, guard.into_raw()))
}
}
#[inline]
pub(crate) fn borrow_mut(&self) -> Result<BorrowMut<[T]>, AccessError> {
unsafe {
let guard = self.shared.as_ref().access.exclusive()?;
let data = Shared::as_data_ptr(self.shared);
let data = NonNull::slice_from_raw_parts(data, self.shared.as_ref().len);
Ok(BorrowMut::new(data, guard.into_raw()))
}
}
#[inline]
pub(crate) fn drop(self) -> Result<(), AccessError> {
unsafe {
self.shared.as_ref().access.try_take()?;
let len = self.shared.as_ref().len;
Shared::drop_values(self.shared, len);
Ok(())
}
}
}
impl<H, T> Dynamic<H, T>
where
H: Clone,
{
pub(crate) fn take(self) -> Result<Self, DynamicTakeError> {
unsafe {
self.shared.as_ref().access.try_take()?;
let len = self.shared.as_ref().len;
let new = Self::alloc(self.rtti().clone(), len)?;
let from = Shared::as_data_ptr(self.shared);
let to = Shared::as_data_ptr(new.shared);
to.copy_from_nonoverlapping(from, len);
Ok(new)
}
}
}
impl<H, T> Drop for Dynamic<H, T> {
fn drop(&mut self) {
unsafe {
Shared::dec(self.shared);
}
}
}
impl<H, T> Clone for Dynamic<H, T> {
#[inline]
fn clone(&self) -> Self {
unsafe {
Shared::inc(self.shared);
}
Self {
shared: self.shared,
}
}
#[inline]
fn clone_from(&mut self, source: &Self) {
if ptr::eq(self.shared.as_ptr(), source.shared.as_ptr()) {
return;
}
let old = replace(&mut self.shared, source.shared);
unsafe {
Shared::dec(old);
Shared::inc(self.shared);
}
}
}
#[repr(C)]
struct Shared<H, T> {
rtti: H,
count: Cell<usize>,
access: Access,
len: usize,
data: [T; 0],
}
impl<H, T> Shared<H, T> {
#[inline]
fn layout(len: usize) -> Result<Layout, LayoutError> {
let array = Layout::array::<T>(len)?;
Layout::from_size_align(
size_of::<Shared<H, T>>() + array.size(),
align_of::<Shared<H, T>>(),
)
}
#[inline]
unsafe fn as_rtti_ptr(this: NonNull<Self>) -> NonNull<H> {
NonNull::new_unchecked(addr_of_mut!((*this.as_ptr()).rtti))
}
#[inline]
unsafe fn as_data_ptr(this: NonNull<Self>) -> NonNull<T> {
NonNull::new_unchecked(addr_of_mut!((*this.as_ptr()).data)).cast::<T>()
}
#[inline]
unsafe fn inc(this: NonNull<Self>) {
let count_ref = &*addr_of!((*this.as_ptr()).count);
let count = count_ref.get();
debug_assert_ne!(
count, 0,
"Reference count of zero should only happen if Shared is incorrectly implemented"
);
if count == usize::MAX {
crate::alloc::abort();
}
count_ref.set(count + 1);
}
#[inline]
unsafe fn dec(this: NonNull<Self>) {
let count_ref = &*addr_of!((*this.as_ptr()).count);
let access = &*addr_of!((*this.as_ptr()).access);
let count = count_ref.get();
debug_assert_ne!(
count, 0,
"Reference count of zero should only happen if Shared is incorrectly implemented"
);
let count = count - 1;
count_ref.set(count);
if count != 0 {
return;
}
let len = (*this.as_ptr()).len;
let Ok(layout) = Self::layout(len) else {
unreachable!();
};
if !access.is_taken() {
Self::drop_values(this, len);
}
if needs_drop::<H>() {
Self::as_rtti_ptr(this).drop_in_place();
}
Global.deallocate(this.cast(), layout);
}
#[inline]
unsafe fn drop_values(this: NonNull<Self>, len: usize) {
if needs_drop::<T>() {
let data = Self::as_data_ptr(this);
NonNull::slice_from_raw_parts(data, len).drop_in_place();
}
}
}
impl<T> Dynamic<Arc<Rtti>, T> {
#[inline]
pub(crate) fn type_hash(&self) -> Hash {
self.rtti().hash
}
#[inline]
pub(crate) fn type_info(&self) -> TypeInfo {
self.rtti().clone().type_info()
}
#[inline]
pub(crate) fn get_field_ref(&self, key: &str) -> Result<Option<BorrowRef<'_, T>>, AccessError> {
let Some(index) = self.rtti().fields.get(key) else {
return Ok(None);
};
self.get_ref(*index)
}
#[inline]
pub(crate) fn get_field_mut(&self, key: &str) -> Result<Option<BorrowMut<'_, T>>, AccessError> {
let Some(index) = self.rtti().fields.get(key) else {
return Ok(None);
};
self.get_mut(*index)
}
#[inline]
pub(crate) fn get_ref(&self, index: usize) -> Result<Option<BorrowRef<'_, T>>, AccessError> {
unsafe {
let shared = self.shared.as_ref();
if index >= shared.len {
return Ok(None);
}
let guard = shared.access.shared()?;
let data = Shared::as_data_ptr(self.shared).add(index);
Ok(Some(BorrowRef::new(data, guard.into_raw())))
}
}
#[inline]
pub(crate) fn get_mut(&self, index: usize) -> Result<Option<BorrowMut<'_, T>>, AccessError> {
unsafe {
let shared = self.shared.as_ref();
if index >= shared.len {
return Ok(None);
}
let guard = shared.access.exclusive()?;
let data = Shared::as_data_ptr(self.shared).add(index);
Ok(Some(BorrowMut::new(data, guard.into_raw())))
}
}
}
impl Dynamic<Arc<Rtti>, Value> {
pub(crate) fn debug_fmt_with(
&self,
f: &mut Formatter,
caller: &mut dyn ProtocolCaller,
) -> VmResult<()> {
let rtti = self.rtti();
let values = vm_try!(self.borrow_ref());
match rtti.kind {
RttiKind::Empty => debug_empty(rtti, f),
RttiKind::Tuple => debug_tuple(rtti, &values, f, caller),
RttiKind::Struct => debug_struct(rtti, &values, f, caller),
}
}
}
fn debug_empty(rtti: &Rtti, f: &mut Formatter) -> VmResult<()> {
vm_try!(write!(f, "{}", rtti.item));
VmResult::Ok(())
}
fn debug_tuple(
rtti: &Rtti,
values: &[Value],
f: &mut Formatter,
caller: &mut dyn ProtocolCaller,
) -> VmResult<()> {
vm_try!(write!(f, "{} (", rtti.item));
let mut first = true;
for value in values.iter() {
if !take(&mut first) {
vm_try!(write!(f, ", "));
}
vm_try!(value.debug_fmt_with(f, caller));
}
vm_try!(write!(f, ")"));
VmResult::Ok(())
}
fn debug_struct(
rtti: &Rtti,
values: &[Value],
f: &mut Formatter,
caller: &mut dyn ProtocolCaller,
) -> VmResult<()> {
vm_try!(write!(f, "{} {{", rtti.item));
let mut first = true;
for (index, field) in values.iter().enumerate() {
let Some((name, _)) = rtti.fields.iter().find(|t| *t.1 == index) else {
continue;
};
if !take(&mut first) {
vm_try!(write!(f, ", "));
}
vm_try!(write!(f, "{name}: "));
vm_try!(field.debug_fmt_with(f, caller));
}
vm_try!(write!(f, "}}"));
VmResult::Ok(())
}
impl IntoOutput for Dynamic<Arc<Rtti>, Value> {
#[inline]
fn into_output(self) -> Result<Value, RuntimeError> {
Ok(Value::from(self))
}
}