use std::alloc;
use std::alloc::Layout;
use std::fmt::Debug;
use std::hash::Hash;
use std::hash::Hasher;
use std::marker::PhantomData;
use std::mem;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::ops::DerefMut;
use std::ptr;
use std::ptr::NonNull;
use std::slice;
use allocative::Allocative;
use crate::pagable::StarlarkDeserialize;
use crate::pagable::StarlarkDeserializeContext;
use crate::pagable::StarlarkSerialize;
use crate::pagable::StarlarkSerializeContext;
#[repr(C)]
struct ThinBoxSliceLayout<T> {
len: usize,
data: [T; 0],
}
impl<T> ThinBoxSliceLayout<T> {
const fn offset_of_data() -> isize {
mem::offset_of!(ThinBoxSliceLayout::<T>, data) as isize
}
}
pub(super) struct AllocatedThinBoxSlice<T: 'static> {
ptr: usize,
phantom: PhantomData<T>,
}
unsafe impl<T: Sync> Sync for AllocatedThinBoxSlice<T> {}
unsafe impl<T: Send> Send for AllocatedThinBoxSlice<T> {}
impl<T: StarlarkSerialize> StarlarkSerialize for AllocatedThinBoxSlice<T> {
fn starlark_serialize(&self, ctx: &mut dyn StarlarkSerializeContext) -> crate::Result<()> {
let data: &[T] = self;
data.len().starlark_serialize(ctx)?;
for item in data {
item.starlark_serialize(ctx)?;
}
Ok(())
}
}
impl<T: StarlarkDeserialize> StarlarkDeserialize for AllocatedThinBoxSlice<T> {
fn starlark_deserialize(ctx: &mut dyn StarlarkDeserializeContext<'_>) -> crate::Result<Self> {
let len = usize::starlark_deserialize(ctx)?;
let mut data = Vec::<T>::with_capacity(len);
for _ in 0..len {
data.push(T::starlark_deserialize(ctx)?);
}
Ok(Self::from_iter(data))
}
}
impl<T: 'static> AllocatedThinBoxSlice<T> {
#[inline]
pub(super) const fn empty() -> AllocatedThinBoxSlice<T> {
AllocatedThinBoxSlice {
ptr: 0,
phantom: PhantomData,
}
}
const fn get_reserved_tag_bit_count() -> usize {
1
}
const fn get_unshifted_tag_bit_mask() -> usize {
let align: usize = std::mem::align_of::<T>();
assert!(align.is_power_of_two());
align - 1
}
const fn get_tag_bit_mask() -> usize {
let mask = Self::get_unshifted_tag_bit_mask()
>> AllocatedThinBoxSlice::<T>::get_reserved_tag_bit_count();
assert!(mask != 0);
mask
}
const fn get_max_short_len() -> usize {
Self::get_tag_bit_mask() + 1
}
#[inline]
fn layout_for_len(len: usize) -> (bool, Layout) {
if len != 0 && len != 1 && len <= Self::get_max_short_len() {
(true, Layout::array::<T>(len).unwrap())
} else {
let (layout, _offset_of_data) = Layout::new::<ThinBoxSliceLayout<T>>()
.extend(Layout::array::<T>(len).unwrap())
.unwrap();
(false, layout)
}
}
#[inline]
fn get_tag_bits(&self) -> usize {
(self.ptr & Self::get_unshifted_tag_bit_mask()) >> Self::get_reserved_tag_bit_count()
}
#[inline]
fn as_ptr(&self) -> *mut T {
(self.ptr & !Self::get_unshifted_tag_bit_mask()) as *mut T
}
#[inline]
fn as_nonnull_ptr(&self) -> *mut T {
let ptr = self.as_ptr();
if ptr.is_null() {
NonNull::<T>::dangling().as_ptr()
} else {
ptr
}
}
#[inline]
fn read_len(&self) -> usize {
if self.as_ptr().is_null() {
return 0;
}
let bits = self.get_tag_bits();
if bits != 0 {
bits + 1
} else {
unsafe {
(*self
.as_ptr()
.byte_offset(-ThinBoxSliceLayout::<T>::offset_of_data())
.cast::<ThinBoxSliceLayout<T>>())
.len
}
}
}
#[inline]
pub(super) fn new_uninit(len: usize) -> AllocatedThinBoxSlice<MaybeUninit<T>> {
if len == 0 {
AllocatedThinBoxSlice::empty()
} else {
let (is_short, layout) = Self::layout_for_len(len);
unsafe {
let alloc = alloc::alloc(layout);
if alloc.is_null() {
alloc::handle_alloc_error(layout);
}
if is_short {
assert!((alloc as usize) & Self::get_unshifted_tag_bit_mask() == 0);
AllocatedThinBoxSlice {
ptr: (alloc as usize) | ((len - 1) << Self::get_reserved_tag_bit_count()),
phantom: PhantomData,
}
} else {
let alloc = alloc as *mut ThinBoxSliceLayout<T>;
(*alloc).len = len;
let data_ptr = alloc.byte_offset(ThinBoxSliceLayout::<T>::offset_of_data());
AllocatedThinBoxSlice {
ptr: data_ptr as usize,
phantom: PhantomData,
}
}
}
}
}
pub const unsafe fn into_inner(self) -> usize {
self.ptr
}
pub unsafe fn from_inner(ptr: usize) -> Self {
Self {
ptr,
phantom: PhantomData,
}
}
}
impl<T: 'static> Deref for AllocatedThinBoxSlice<T> {
type Target = [T];
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.as_nonnull_ptr(), self.read_len()) }
}
}
impl<T: 'static> DerefMut for AllocatedThinBoxSlice<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { slice::from_raw_parts_mut(self.as_nonnull_ptr(), self.read_len()) }
}
}
impl<T> AllocatedThinBoxSlice<MaybeUninit<T>> {
#[inline]
unsafe fn assume_init(self) -> AllocatedThinBoxSlice<T> {
AllocatedThinBoxSlice {
ptr: self.ptr,
phantom: PhantomData,
}
}
}
impl<T: 'static> AllocatedThinBoxSlice<T> {
#[inline]
pub(super) fn run_drop(self) {
unsafe {
let len = self.read_len();
if len != 0 {
let slice = ptr::slice_from_raw_parts_mut(self.as_nonnull_ptr(), len);
ptr::drop_in_place(slice);
let mut alloc = self.as_ptr().cast::<u8>();
let (is_short, layout) = Self::layout_for_len(len);
if !is_short {
alloc = alloc.byte_offset(-ThinBoxSliceLayout::<T>::offset_of_data());
}
alloc::dealloc(alloc, layout);
}
}
}
}
impl<T: 'static> Default for AllocatedThinBoxSlice<T> {
#[inline]
fn default() -> Self {
AllocatedThinBoxSlice::empty()
}
}
impl<T: Debug> Debug for AllocatedThinBoxSlice<T> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<[T] as Debug>::fmt(&**self, f)
}
}
impl<T: PartialEq> PartialEq for AllocatedThinBoxSlice<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
<[T] as PartialEq>::eq(&**self, &**other)
}
}
impl<T: Eq> Eq for AllocatedThinBoxSlice<T> {}
impl<T: PartialOrd> PartialOrd for AllocatedThinBoxSlice<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
<[T] as PartialOrd>::partial_cmp(&**self, &**other)
}
}
impl<T: Hash> Hash for AllocatedThinBoxSlice<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
<[T] as Hash>::hash(&**self, state)
}
}
impl<T> FromIterator<T> for AllocatedThinBoxSlice<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, upper) = iter.size_hint();
if Some(lower) == upper {
let mut thin = AllocatedThinBoxSlice::<T>::new_uninit(lower);
let mut i = 0;
for item in iter {
assert!(i < lower, "iterator produced more than promised");
MaybeUninit::write(&mut thin[i], item);
i += 1;
}
assert_eq!(i, lower, "iterator produced less than promised");
unsafe { thin.assume_init() }
} else {
let vec = Vec::from_iter(iter);
Self::from_iter(vec)
}
}
}
impl<T: Allocative> Allocative for AllocatedThinBoxSlice<T> {
fn visit<'a, 'b: 'a>(&self, visitor: &'a mut allocative::Visitor<'b>) {
let mut visitor = visitor.enter_self_sized::<Self>();
{
let ptr_key = allocative::Key::new("ptr");
if self.is_empty() {
visitor.visit_simple(ptr_key, mem::size_of_val(&self.ptr));
} else {
let mut visitor =
visitor.enter_unique(allocative::Key::new("ptr"), mem::size_of_val(&self.ptr));
{
let (is_short, layout) = Self::layout_for_len(self.len());
let mut visitor = visitor.enter(allocative::Key::new("alloc"), layout.size());
if !is_short {
visitor.visit_simple(allocative::Key::new("len"), mem::size_of::<usize>());
}
{
let mut visitor = visitor
.enter(allocative::Key::new("data"), mem::size_of_val::<[_]>(self));
visitor.visit_slice::<T>(self);
visitor.exit();
}
visitor.exit();
}
visitor.exit();
}
}
visitor.exit();
}
}
#[cfg(test)]
mod tests {
use super::AllocatedThinBoxSlice;
#[test]
fn test_empty() {
let thin = AllocatedThinBoxSlice::<String>::empty();
assert_eq!(0, thin.len());
thin.run_drop();
}
#[test]
fn test_from_iter_sized() {
let thin =
AllocatedThinBoxSlice::from_iter(["a".to_owned(), "bb".to_owned(), "ccc".to_owned()]);
assert_eq!(["a".to_owned(), "bb".to_owned(), "ccc".to_owned()], *thin);
thin.run_drop();
}
#[test]
fn test_from_iter_unknown_size() {
let thin = AllocatedThinBoxSlice::from_iter(
["a".to_owned(), "b".to_owned(), "c".to_owned()]
.into_iter()
.filter(|_| true),
);
assert_eq!(["a".to_owned(), "b".to_owned(), "c".to_owned()], *thin);
thin.run_drop();
}
#[test]
fn test_stress() {
for i in 0..1000 {
let thin = AllocatedThinBoxSlice::from_iter((0..i).map(|j| j.to_string()));
assert_eq!(i, thin.len());
assert_eq!((0..i).map(|j| j.to_string()).collect::<Vec<_>>(), *thin);
thin.run_drop();
}
}
}