#![allow(clippy::unusual_byte_groupings)]
use std::cell::Cell;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem;
use std::num::NonZeroUsize;
use allocative::Allocative;
use dupe::Dupe;
use either::Either;
use static_assertions::assert_eq_size;
use crate::cast;
use crate::values::int::pointer_i32::PointerI32;
use crate::values::layout::heap::repr::AValueHeader;
use crate::values::layout::heap::repr::AValueOrForward;
use crate::values::types::int::inline_int::InlineInt;
#[derive(Clone, Copy, Dupe, PartialEq, Eq, Hash, Allocative)]
pub(crate) struct RawPointer(pub(crate) NonZeroUsize);
impl Debug for RawPointer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("RawPointer")
.field(&format_args!("0x{:x}", self.ptr_value()))
.finish()
}
}
impl RawPointer {
#[inline]
pub(crate) unsafe fn new_unchecked(ptr: usize) -> RawPointer {
debug_assert!(ptr != 0);
let ptr = RawPointer(NonZeroUsize::new_unchecked(ptr));
let _ignore = PointerTags::from_pointer(ptr);
ptr
}
#[inline]
pub(crate) fn new_int(i: InlineInt) -> RawPointer {
let ptr = ((i.to_i32() as isize) << INT_SHIFT) as usize | TAG_INT;
unsafe { Self::new_unchecked(ptr) }
}
#[inline]
pub(crate) fn new_unfrozen(ptr: &AValueHeader, is_string: bool) -> RawPointer {
let ptr = cast::ptr_to_usize(ptr);
debug_assert!(ptr & TAG_MASK == 0);
let ptr = if is_string { ptr | TAG_STR } else { ptr };
let ptr = ptr | TAG_UNFROZEN;
unsafe { Self::new_unchecked(ptr) }
}
#[inline]
pub(crate) fn new_frozen(ptr: &AValueHeader, is_string: bool) -> RawPointer {
let ptr = cast::ptr_to_usize(ptr);
debug_assert!(ptr & TAG_MASK == 0);
let ptr = if is_string { ptr | TAG_STR } else { ptr };
unsafe { Self::new_unchecked(ptr) }
}
#[inline]
pub(crate) fn ptr_value(self) -> usize {
self.0.get()
}
#[inline]
pub(crate) fn tags(self) -> PointerTags {
PointerTags::from_pointer(self)
}
#[inline]
pub(crate) fn is_str(self) -> bool {
self.tags().is_str()
}
#[inline]
pub(crate) fn is_int(self) -> bool {
self.tags().is_int()
}
#[inline]
pub(crate) fn is_unfrozen(self) -> bool {
self.tags().is_unfrozen()
}
#[inline]
pub(crate) fn unpack_int(self) -> Option<InlineInt> {
if !self.is_int() {
None
} else {
unsafe { Some(self.unpack_int_unchecked()) }
}
}
#[inline]
pub(crate) unsafe fn unpack_pointer_i32_unchecked(self) -> &'static PointerI32 {
debug_assert!(self.is_int());
debug_assert!(self.0.get() & !INT_DATA_MASK == TAG_INT);
PointerI32::from_raw_pointer_unchecked(self)
}
#[inline]
pub(crate) unsafe fn unpack_int_unchecked(self) -> InlineInt {
debug_assert!(self.is_int());
debug_assert!(self.0.get() & !INT_DATA_MASK == TAG_INT);
InlineInt::new_unchecked(((self.0.get() as isize) >> INT_SHIFT) as i32)
}
#[inline]
pub(crate) unsafe fn unpack_ptr_no_int_unchecked<'v>(self) -> &'v AValueOrForward {
debug_assert!(!self.is_int());
let ptr = self.0.get() & !(TAG_STR | TAG_UNFROZEN);
cast::usize_to_ptr(ptr)
}
}
#[derive(Clone, Copy, Dupe)]
pub(crate) struct Pointer<'p> {
ptr: RawPointer,
_phantom: PhantomData<Cell<&'p AValueHeader>>,
}
#[derive(Clone, Copy, Dupe)]
pub(crate) struct FrozenPointer<'p> {
ptr: RawPointer,
phantom: PhantomData<&'p AValueHeader>,
}
fn _test_lifetime_covariant<'a>(p: FrozenPointer<'static>) -> FrozenPointer<'a> {
p
}
assert_eq_size!(Pointer<'static>, usize);
assert_eq_size!(Option<Pointer<'static>>, usize);
assert_eq_size!(FrozenPointer<'static>, usize);
assert_eq_size!(Option<FrozenPointer<'static>>, usize);
#[allow(dead_code)] const TAG_BITS: usize = 3;
const TAG_MASK: usize = 0b111;
#[allow(clippy::assertions_on_constants)]
const _: () = assert!(TAG_MASK == (1 << TAG_BITS) - 1);
const TAG_INT: usize = 0b010;
const TAG_STR: usize = 0b100;
const TAG_UNFROZEN: usize = 0b001;
#[repr(usize)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub(crate) enum PointerTags {
Int = TAG_INT,
StrUnfrozen = TAG_STR | TAG_UNFROZEN,
StrFrozen = TAG_STR,
OtherUnfrozen = TAG_UNFROZEN,
OtherFrozen = 0,
}
impl PointerTags {
#[inline]
unsafe fn from_usize_unchecked(x: usize) -> Self {
debug_assert!(
x == PointerTags::Int as usize
|| x == PointerTags::StrUnfrozen as usize
|| x == PointerTags::StrFrozen as usize
|| x == PointerTags::OtherUnfrozen as usize
|| x == PointerTags::OtherFrozen as usize
);
unsafe { mem::transmute(x) }
}
#[inline]
fn from_pointer(ptr: RawPointer) -> Self {
unsafe { Self::from_usize_unchecked(ptr.0.get() & TAG_MASK) }
}
#[inline]
fn to_usize(self) -> usize {
self as usize
}
#[inline]
fn is_str(self) -> bool {
self.to_usize() & TAG_STR != 0
}
#[inline]
fn is_int(self) -> bool {
self == PointerTags::Int
}
#[inline]
fn is_unfrozen(self) -> bool {
self.to_usize() & TAG_UNFROZEN != 0
}
}
#[repr(usize)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum _FrozenPointerTags {
Int = TAG_INT,
Str = TAG_STR,
Other = 0,
}
const INT_SHIFT: usize = mem::size_of::<usize>() * 8 - InlineInt::BITS;
const INT_DATA_MASK: usize = ((1usize << InlineInt::BITS) - 1) << INT_SHIFT;
#[allow(clippy::assertions_on_constants)]
const _: () = assert!(INT_SHIFT >= TAG_BITS);
#[inline]
unsafe fn untag_pointer<'a>(x: usize) -> &'a AValueOrForward {
cast::usize_to_ptr(x & !TAG_MASK)
}
impl<'p> Pointer<'p> {
#[inline]
unsafe fn new(ptr: RawPointer) -> Pointer<'p> {
Pointer {
ptr,
_phantom: PhantomData,
}
}
#[inline]
pub(crate) unsafe fn new_unfrozen_usize_with_str_tag(x: usize) -> Self {
debug_assert!((x & TAG_MASK & !TAG_STR) == 0);
Self::new(RawPointer::new_unchecked(x | TAG_UNFROZEN))
}
#[inline]
pub(crate) fn new_unfrozen(x: &'p AValueHeader, is_string: bool) -> Self {
unsafe { Self::new(RawPointer::new_unfrozen(x, is_string)) }
}
#[inline]
pub(crate) fn is_str(self) -> bool {
self.ptr.is_str()
}
#[inline]
pub(crate) fn is_unfrozen(self) -> bool {
self.ptr.is_unfrozen()
}
#[inline]
pub(crate) fn unpack(self) -> Either<&'p AValueOrForward, &'static PointerI32> {
if !self.ptr.is_int() {
Either::Left(unsafe { self.ptr.unpack_ptr_no_int_unchecked() })
} else {
Either::Right(unsafe { PointerI32::from_raw_pointer_unchecked(self.ptr) })
}
}
#[inline]
pub(crate) fn unpack_int(self) -> Option<InlineInt> {
self.ptr.unpack_int()
}
#[inline]
pub(crate) fn unpack_ptr(self) -> Option<&'p AValueOrForward> {
if !self.ptr.is_int() {
Some(unsafe { untag_pointer(self.ptr.0.get()) })
} else {
None
}
}
#[inline]
pub(crate) unsafe fn unpack_ptr_no_int_unchecked(self) -> &'p AValueOrForward {
let p = self.ptr.0.get();
debug_assert!(!self.ptr.is_int());
untag_pointer(p)
}
#[inline]
pub(crate) unsafe fn unpack_pointer_i32_unchecked(self) -> &'static PointerI32 {
self.ptr.unpack_pointer_i32_unchecked()
}
#[inline]
pub(crate) fn ptr_eq(self, other: Pointer<'_>) -> bool {
self.ptr == other.ptr
}
#[inline]
pub(crate) fn raw(self) -> RawPointer {
self.ptr
}
#[inline]
pub(crate) unsafe fn cast_lifetime<'p2>(self) -> Pointer<'p2> {
Pointer {
ptr: self.ptr,
_phantom: PhantomData,
}
}
#[inline]
pub(crate) unsafe fn to_frozen_pointer_unchecked(self) -> FrozenPointer<'p> {
FrozenPointer::new(self.ptr)
}
}
impl<'p> FrozenPointer<'p> {
#[inline]
pub(crate) unsafe fn new(ptr: RawPointer) -> FrozenPointer<'p> {
debug_assert!(!ptr.is_unfrozen());
FrozenPointer {
ptr,
phantom: PhantomData,
}
}
#[inline]
pub(crate) fn new_frozen_usize_with_str_tag(x: usize) -> Self {
debug_assert!((x & TAG_MASK & !TAG_STR) == 0);
unsafe { Self::new(RawPointer::new_unchecked(x)) }
}
#[inline]
pub(crate) fn new_frozen(x: &'p AValueHeader, is_str: bool) -> Self {
unsafe { Self::new(RawPointer::new_frozen(x, is_str)) }
}
#[inline]
pub(crate) fn new_int(x: InlineInt) -> Self {
unsafe { Self::new(RawPointer::new_int(x)) }
}
#[inline]
pub(crate) fn to_pointer(self) -> Pointer<'p> {
Pointer {
ptr: self.ptr,
_phantom: PhantomData,
}
}
#[inline]
pub(crate) fn raw(self) -> RawPointer {
self.ptr
}
#[inline]
pub(crate) unsafe fn unpack_ptr_no_int_unchecked(self) -> &'p AValueOrForward {
debug_assert!(!self.ptr.is_int());
self.ptr.unpack_ptr_no_int_unchecked()
}
#[inline]
pub(crate) unsafe fn unpack_pointer_i32_unchecked(self) -> &'static PointerI32 {
self.ptr.unpack_pointer_i32_unchecked()
}
#[inline]
pub(crate) unsafe fn unpack_ptr_no_int_no_str_unchecked(self) -> &'p AValueOrForward {
debug_assert!(self.ptr.tags() == PointerTags::OtherFrozen);
cast::usize_to_ptr(self.ptr.0.get())
}
}
#[cfg(test)]
#[test]
fn test_int_tag() {
fn check(x: InlineInt) {
assert_eq!(x, RawPointer::new_int(x).unpack_int().unwrap());
}
for x in -10..10 {
check(InlineInt::try_from(x).ok().unwrap());
}
check(InlineInt::MAX);
check(InlineInt::MIN);
}