use core::alloc::{Layout, LayoutError};
use core::fmt::{self, Debug, Formatter};
use core::marker::Unsize;
use core::mem::{align_of, size_of, ManuallyDrop, MaybeUninit};
use core::ops::{Deref, DerefMut};
use core::ptr::NonNull;
use crate::storage::{LayoutSpec, Storage};
pub struct ObjectLayout(());
impl LayoutSpec for ObjectLayout {
fn layout_with_capacity(bytes: usize) -> Result<Layout, LayoutError> {
Layout::from_size_align(bytes, 8)
}
}
#[repr(align(8))]
pub struct Align8;
#[repr(align(16))]
pub struct Align16;
#[repr(align(32))]
pub struct Align32;
#[repr(align(64))]
pub struct Align64;
#[repr(align(128))]
pub struct Align128;
#[repr(C)]
pub union InlineStorage<A, const N: usize> {
force_alignment: ManuallyDrop<A>,
data: [MaybeUninit<u8>; N],
}
unsafe impl<A, const N: usize> Storage<ObjectLayout> for InlineStorage<A, N> {
fn capacity(&self) -> usize {
N
}
fn get_ptr(&self) -> *const u8 {
unsafe { self.data.as_ptr().cast() }
}
fn get_mut_ptr(&mut self) -> *mut u8 {
unsafe { self.data.as_mut_ptr().cast() }
}
}
pub struct Object<T: ?Sized, S: Storage<ObjectLayout>> {
meta: NonNull<T>,
buf: S,
}
pub type InlineObject<T, const N: usize> = Object<T, InlineStorage<Align8, N>>;
impl<T: ?Sized, S: Storage<ObjectLayout>> Deref for Object<T, S> {
type Target = T;
fn deref(&self) -> &Self::Target {
let dangling = self.meta.as_ptr() as *const T;
let fat_ptr = dangling.set_ptr_value(self.buf.get_ptr());
unsafe { fat_ptr.as_ref().unwrap() }
}
}
impl<T: ?Sized, S: Storage<ObjectLayout>> DerefMut for Object<T, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
let dangling = self.meta.as_ptr() as *mut T;
let fat_ptr = dangling.set_ptr_value(self.buf.get_mut_ptr());
unsafe { fat_ptr.as_mut().unwrap() }
}
}
impl<T: ?Sized + Debug, S: Storage<ObjectLayout>> Debug for Object<T, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.deref().fmt(f)
}
}
impl<T: ?Sized, S: Storage<ObjectLayout>> Drop for Object<T, S> {
fn drop(&mut self) {
unsafe { core::ptr::addr_of_mut!(**self).drop_in_place() };
}
}
impl<T: ?Sized, A, const N: usize> Object<T, InlineStorage<A, N>> {
#[inline]
fn check_layout<U: Sized + Unsize<T>>() {
#[inline(never)]
#[cold]
fn value_too_large(cap: usize, actual: usize) {
panic!(
"the object can only hold {} bytes, but the supplied value is {} bytes",
cap, actual
);
}
#[inline(never)]
#[cold]
fn value_too_strictly_aligned(guaranteed: usize, required: usize) {
panic!("the object can only guarantee an alignment of {}, but the supplied value requires {}", guaranteed, required);
}
if size_of::<U>() > N {
value_too_large(N, size_of::<U>());
}
if align_of::<U>() > align_of::<A>() {
value_too_strictly_aligned(align_of::<A>(), align_of::<U>());
}
}
pub fn new<U: Sized + Unsize<T>>(val: U) -> Self {
Self::check_layout::<U>();
let mut result = Object {
meta: NonNull::<U>::dangling() as NonNull<T>,
buf: InlineStorage {
data: [MaybeUninit::uninit(); N],
},
};
let ptr = result.buf.get_mut_ptr().cast::<U>();
unsafe { ptr.write(val) };
result
}
pub fn set<U: Sized + Unsize<T>>(&mut self, val: U) {
Self::check_layout::<U>();
unsafe { core::ptr::addr_of_mut!(**self).drop_in_place() };
self.meta = NonNull::<U>::dangling() as NonNull<T>;
let ptr = self.buf.get_mut_ptr().cast::<U>();
unsafe { ptr.write(val) };
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::align_of;
#[test]
fn inline_storage_layout() {
fn test_layout<A, const B: usize>(align: usize) {
assert_eq!(align_of::<A>(), align);
assert_eq!(align_of::<InlineStorage<A, B>>(), align);
let expected_size = (B + (align - 1)) & !(align - 1);
assert_eq!(size_of::<InlineStorage<A, B>>(), expected_size);
}
test_layout::<Align8, 5>(8);
test_layout::<Align8, 13>(8);
test_layout::<Align8, 17>(8);
test_layout::<Align16, 1>(16);
test_layout::<Align16, 39>(16);
test_layout::<Align32, 20>(32);
test_layout::<Align32, 101>(32);
test_layout::<Align64, 40>(64);
test_layout::<Align64, 80>(64);
test_layout::<Align128, 50>(128);
test_layout::<Align128, 150>(128);
}
#[test]
fn drops_correctly() {
use crate::test_utils::*;
let drop_count = DropCounter::new();
{
let mut obj: InlineObject<dyn Debug, 8> = InlineObject::new(drop_count.new_droppable(()));
obj.set(drop_count.new_droppable(()));
assert_eq!(drop_count.dropped(), 1);
}
assert_eq!(drop_count.dropped(), 2);
}
}