#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
use core::marker::PhantomData;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(feature = "std")]
use std::boxed::Box;
pub use tagged_dispatch_macros::tagged_dispatch;
#[cfg(feature = "allocator-bumpalo")]
pub use bumpalo;
#[cfg(feature = "allocator-typed-arena")]
pub use typed_arena;
#[repr(transparent)]
pub struct TaggedPtr<T> {
ptr: usize,
_phantom: PhantomData<T>,
}
impl<T> TaggedPtr<T> {
const TAG_BITS: usize = 7;
const TAG_SHIFT: usize = 64 - Self::TAG_BITS;
const TAG_MASK: usize = ((1 << Self::TAG_BITS) - 1) << Self::TAG_SHIFT;
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
const PTR_MASK: usize = !Self::TAG_MASK;
pub const MAX_VARIANTS: usize = 1 << Self::TAG_BITS;
#[inline(always)]
pub fn new(ptr: *mut T, tag: u8) -> Self {
debug_assert!(
tag < Self::MAX_VARIANTS as u8,
"Tag must be less than 128 (7 bits)"
);
let addr = ptr as usize;
debug_assert_eq!(
addr & Self::TAG_MASK,
0,
"Pointer already has high bits set!"
);
Self {
ptr: addr | ((tag as usize) << Self::TAG_SHIFT),
_phantom: PhantomData,
}
}
#[inline(always)]
pub fn tag(&self) -> u8 {
((self.ptr & Self::TAG_MASK) >> Self::TAG_SHIFT) as u8
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
#[inline(always)]
pub fn ptr(&self) -> *mut T {
self.ptr as *mut T
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
#[inline(always)]
pub fn ptr(&self) -> *mut T {
(self.ptr & Self::PTR_MASK) as *mut T
}
#[doc(hidden)]
#[inline(always)]
pub fn untagged_ptr(&self) -> *mut T {
const PTR_MASK: usize = !(0x7F << 57);
(self.ptr & PTR_MASK) as *mut T
}
#[inline(always)]
pub unsafe fn as_ref(&self) -> &T {
unsafe { &*self.ptr() }
}
#[inline(always)]
pub unsafe fn as_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr() }
}
#[inline(always)]
pub fn is_null(&self) -> bool {
self.ptr() as usize == 0
}
}
unsafe impl<T: Send> Send for TaggedPtr<T> {}
unsafe impl<T: Sync> Sync for TaggedPtr<T> {}
impl<T> Clone for TaggedPtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for TaggedPtr<T> {}
impl<T> core::fmt::Debug for TaggedPtr<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TaggedPtr")
.field("tag", &self.tag())
.field("ptr", &format_args!("{:p}", self.ptr()))
.finish()
}
}
impl<T> core::cmp::PartialEq for TaggedPtr<T> {
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr
}
}
impl<T> core::cmp::Eq for TaggedPtr<T> {}
impl<T> core::cmp::PartialOrd for TaggedPtr<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> core::cmp::Ord for TaggedPtr<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.ptr.cmp(&other.ptr)
}
}
pub trait TaggedAllocator {
fn alloc<T>(&self, value: T) -> *mut T;
}
#[cfg(feature = "bumpalo")]
impl TaggedAllocator for bumpalo::Bump {
#[inline]
fn alloc<T>(&self, value: T) -> *mut T {
bumpalo::Bump::alloc(self, value) as *mut T
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ArenaStats {
pub allocated_bytes: usize,
pub chunk_capacity: usize,
}
pub trait ArenaBuilder<'a>: Sized {
fn new() -> Self;
fn reset(&mut self);
fn clear(&mut self);
fn stats(&self) -> ArenaStats;
}
pub struct BoxAllocator;
impl TaggedAllocator for BoxAllocator {
#[inline]
fn alloc<T>(&self, value: T) -> *mut T {
Box::into_raw(Box::new(value))
}
}
#[doc(hidden)]
pub mod __private {
pub use core::mem;
pub use core::ptr;
pub use core::marker::PhantomData;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tag_extraction() {
let ptr = core::ptr::null_mut::<u32>();
let tagged = TaggedPtr::new(ptr, 127);
assert_eq!(tagged.tag(), 127);
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
let returned_ptr = tagged.ptr() as usize;
let expected = ptr as usize | (127usize << TaggedPtr::<u32>::TAG_SHIFT);
assert_eq!(returned_ptr, expected);
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
assert_eq!(tagged.ptr(), ptr);
}
}
#[test]
fn test_tag_preservation() {
let value = Box::new(42u32);
let ptr = Box::into_raw(value);
for tag in 0..128u8 {
let tagged = TaggedPtr::new(ptr, tag);
assert_eq!(tagged.tag(), tag);
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
let returned_ptr = tagged.ptr() as usize;
let expected = ptr as usize | ((tag as usize) << TaggedPtr::<u32>::TAG_SHIFT);
assert_eq!(returned_ptr, expected);
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
assert_eq!(tagged.ptr(), ptr);
}
}
unsafe { let _ = Box::from_raw(ptr); }
}
#[test]
fn test_size() {
assert_eq!(core::mem::size_of::<TaggedPtr<()>>(), 8);
}
#[test]
#[should_panic(expected = "Tag must be less than 128")]
fn test_tag_overflow() {
let ptr = core::ptr::null_mut::<u32>();
let _tagged = TaggedPtr::new(ptr, 128);
}
#[cfg(feature = "bumpalo")]
#[test]
fn test_bumpalo_allocator() {
use bumpalo::Bump;
let arena = Bump::new();
let value = 42u32;
let ptr = arena.alloc(value);
let tagged = TaggedPtr::new(ptr, 5);
assert_eq!(tagged.tag(), 5);
unsafe {
assert_eq!(*tagged.as_ref(), 42);
}
}
}