use crate::discriminant::{
Discriminant, DISCRIMINANT_MASK, MAX_DISCRIMINANT, MAX_POINTER_VALUE, POINTER_WIDTH,
};
use core::fmt;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct TaggedPointer {
tagged_ptr: usize,
}
impl TaggedPointer {
#[inline]
#[allow(clippy::absurd_extreme_comparisons)]
pub fn new(ptr: usize, discriminant: Discriminant) -> Self {
assert!(
discriminant <= MAX_DISCRIMINANT,
"Attempted to store a discriminant of {} while the max value is {}",
discriminant,
MAX_DISCRIMINANT,
);
assert!(
ptr <= MAX_POINTER_VALUE,
"If you are receiving this error, then your hardware uses more than {} bits of a pointer to store addresses. \
It is recommended that you use a different feature for the `tagged-box` crate using `features = [\"{}bits\"]` or above.
",
POINTER_WIDTH,
POINTER_WIDTH + 1,
);
let tagged_ptr = unsafe { Self::store_discriminant_unchecked(ptr, discriminant) };
Self { tagged_ptr }
}
#[inline]
pub unsafe fn new_unchecked(ptr: usize, discriminant: Discriminant) -> Self {
let tagged_ptr = Self::store_discriminant_unchecked(ptr, discriminant);
Self { tagged_ptr }
}
#[inline]
pub const fn discriminant(self) -> Discriminant {
Self::fetch_discriminant(self.tagged_ptr)
}
#[inline]
#[allow(clippy::should_implement_trait)]
pub unsafe fn as_ref<T>(&self) -> &T {
&*(Self::strip_discriminant(self.tagged_ptr) as *const T)
}
#[inline]
pub unsafe fn as_mut_ref<T>(&mut self) -> &mut T {
&mut *(Self::strip_discriminant(self.tagged_ptr) as *mut T)
}
#[inline]
pub const fn as_usize(self) -> usize {
Self::strip_discriminant(self.tagged_ptr)
}
#[inline]
pub const fn as_raw_usize(self) -> usize {
self.tagged_ptr
}
#[inline]
pub const fn as_ptr<T>(self) -> *const T {
Self::strip_discriminant(self.tagged_ptr) as *const T
}
#[inline]
pub fn as_mut_ptr<T>(self) -> *mut T {
Self::strip_discriminant(self.tagged_ptr) as *mut T
}
#[inline]
#[allow(clippy::absurd_extreme_comparisons)]
pub fn store_discriminant(pointer: usize, discriminant: Discriminant) -> usize {
assert!(
discriminant <= MAX_DISCRIMINANT,
"Attempted to store a discriminant of {} while the max value is {}",
discriminant,
MAX_DISCRIMINANT,
);
assert!(
pointer <= MAX_POINTER_VALUE,
"If you are receiving this error, then your hardware uses more than {} bits of a pointer to store addresses. \
It is recommended that you use a different feature for the `tagged-box` crate using `features = [\"{}bits\"]` or above.
",
POINTER_WIDTH,
POINTER_WIDTH + 1,
);
pointer | ((discriminant as usize) << POINTER_WIDTH)
}
#[inline]
pub unsafe fn store_discriminant_unchecked(
pointer: usize,
discriminant: Discriminant,
) -> usize {
pointer | ((discriminant as usize) << POINTER_WIDTH)
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub const fn fetch_discriminant(pointer: usize) -> Discriminant {
(pointer >> POINTER_WIDTH) as Discriminant
}
#[inline]
pub const fn strip_discriminant(pointer: usize) -> usize {
pointer & DISCRIMINANT_MASK
}
}
impl fmt::Debug for TaggedPointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TaggedPointer")
.field("raw", &(self.as_raw_usize() as *const ()))
.field("ptr", &self.as_ptr::<()>())
.field("discriminant", &self.discriminant())
.finish()
}
}
impl fmt::Pointer for TaggedPointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.as_ptr::<()>(), f)
}
}
impl_fmt!(TaggedPointer => LowerHex, UpperHex, Binary, Octal);
#[cfg(test)]
mod tests {
use super::*;
use crate::discriminant;
use alloc::string::String;
use core::slice;
#[test]
fn utility_functions() {
let ptr = 0xF00D_BEEF;
let discrim = discriminant::MAX_DISCRIMINANT / 2;
let stored = TaggedPointer::new(ptr, discrim).as_raw_usize();
assert_eq!(TaggedPointer::strip_discriminant(stored), ptr);
assert_eq!(TaggedPointer::fetch_discriminant(stored), discrim);
assert_eq!(TaggedPointer::store_discriminant(ptr, discrim), stored);
}
#[test]
fn tagged_pointer() {
let integer = 100i32;
let int_ptr = &integer as *const _ as usize;
let discriminant = 10;
let ptr = TaggedPointer::new(int_ptr, discriminant);
assert_eq!(ptr.discriminant(), discriminant);
assert_eq!(ptr.as_usize(), int_ptr);
unsafe {
assert_eq!(ptr.as_ref::<i32>(), &integer);
}
}
#[test]
fn max_pointer() {
let ptr = discriminant::MAX_POINTER_VALUE;
let discriminant = discriminant::MAX_DISCRIMINANT;
let tagged = TaggedPointer::new(ptr, discriminant);
assert_eq!(tagged.discriminant(), discriminant);
assert_eq!(tagged.as_usize(), ptr);
}
#[test]
fn min_pointer() {
let ptr: usize = 0;
let discriminant = discriminant::MAX_DISCRIMINANT;
let tagged = TaggedPointer::new(ptr, discriminant);
assert_eq!(tagged.discriminant(), discriminant);
assert_eq!(tagged.as_usize(), ptr);
}
#[test]
fn max_discriminant() {
let integer = 100usize;
let int_ptr = &integer as *const _ as usize;
let discriminant = discriminant::MAX_DISCRIMINANT;
let ptr = TaggedPointer::new(int_ptr, discriminant);
assert_eq!(ptr.discriminant(), discriminant);
assert_eq!(ptr.as_usize(), int_ptr);
unsafe {
assert_eq!(ptr.as_ref::<usize>(), &integer);
}
}
#[test]
fn min_discriminant() {
let integer = 100usize;
let int_ptr = &integer as *const _ as usize;
let discriminant = 0;
let ptr = TaggedPointer::new(int_ptr, discriminant);
assert_eq!(ptr.discriminant(), discriminant);
assert_eq!(ptr.as_usize(), int_ptr);
unsafe {
assert_eq!(ptr.as_ref::<usize>(), &integer);
}
}
#[test]
fn string_pointer() {
let string = String::from("Hello world!");
let str_ptr = string.as_ptr() as usize;
let discriminant = discriminant::MAX_DISCRIMINANT;
let ptr = TaggedPointer::new(str_ptr, discriminant);
assert_eq!(ptr.discriminant(), discriminant);
assert_eq!(ptr.as_usize(), str_ptr);
unsafe {
let temp_str = slice::from_raw_parts(ptr.as_ptr::<u8>(), string.len());
assert_eq!(core::str::from_utf8(temp_str).unwrap(), &string);
}
}
#[test]
#[should_panic]
#[cfg_attr(miri, ignore)]
fn oversized_discriminant() {
let pointer = 0xF00D_BEEF;
let discriminant = if let Some(discrim) = discriminant::MAX_DISCRIMINANT.checked_add(1) {
discrim
} else {
panic!("Adding one to discriminant would overflow type, aborting test");
};
TaggedPointer::new(pointer, discriminant);
}
}