use core::{marker::PhantomData, mem::size_of};
use sptr::Strict;
use crate::{
config::{PtrCfg, PtrCfgExt},
PackedPtrError,
};
const PTR_SIZE_BITS: usize = size_of::<*const ()>() * 8;
#[repr(transparent)]
pub struct PackedPtr<T, C: PtrCfg>(*const T, PhantomData<C>);
impl<T, C: PtrCfg> PackedPtr<T, C> {
#[must_use]
pub fn bits() -> usize {
C::bits::<T>()
}
#[allow(clippy::not_unsafe_ptr_arg_deref, clippy::needless_pass_by_value)]
pub fn new(ptr: *const T, data: usize, _cfg: C) -> Result<Self, PackedPtrError> {
if Strict::addr(ptr) & C::lsb_bits_mask::<T>() != 0 {
return Err(PackedPtrError::UnalignedAddress);
}
if data >= 1 << Self::bits() {
return Err(PackedPtrError::DataOverflow);
}
if !C::check_ptr(ptr) {
return Err(PackedPtrError::UnsafeConfig);
}
Ok(unsafe { Self::new_unchecked(ptr, data) })
}
pub unsafe fn new_unchecked(ptr: *const T, data: usize) -> Self {
let trunc_data = data & ((1 << Self::bits()) - 1);
let upper = (trunc_data << (PTR_SIZE_BITS - Self::bits())) & C::msb_bits_mask();
let lower = trunc_data & C::lsb_bits_mask::<T>();
let ptr = Strict::map_addr(ptr, |addr| addr | upper | lower);
Self(ptr, PhantomData)
}
#[must_use]
pub fn get(self) -> (*const T, usize) {
let ptr = self.ptr();
let data = self.data();
(ptr, data)
}
#[must_use]
pub fn ptr(self) -> *const T {
Strict::map_addr(self.0, |addr| {
addr & !C::msb_bits_mask() & !C::lsb_bits_mask::<T>()
})
}
#[must_use]
pub fn data(self) -> usize {
let addr = Strict::addr(self.0);
let upper = addr & C::msb_bits_mask();
let lower = addr & C::lsb_bits_mask::<T>();
(upper >> (PTR_SIZE_BITS - Self::bits())) | lower
}
}
impl<T, C: PtrCfg> core::fmt::Debug for PackedPtr<T, C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("PackedPtr")
.field(&self.ptr())
.field(&self.data())
.finish()
}
}
impl<T, C: PtrCfg> core::fmt::Pointer for PackedPtr<T, C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Pointer::fmt(&self.ptr(), f)
}
}
impl<T, C: PtrCfg> Copy for PackedPtr<T, C> {}
impl<T, C: PtrCfg> Clone for PackedPtr<T, C> {
fn clone(&self) -> Self {
*self
}
}
impl<T, C: PtrCfg> PartialEq for PackedPtr<T, C> {
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}
impl<T, C: PtrCfg> Eq for PackedPtr<T, C> {}
impl<T, C: PtrCfg> core::hash::Hash for PackedPtr<T, C> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.get().hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::AlignOnly;
#[test]
fn round_trip() {
let data = 0xdead_beef_u32;
let packed = 2;
let ptr = PackedPtr::new(&data, packed, AlignOnly).unwrap();
assert_eq!(data, unsafe { *ptr.ptr() });
assert_eq!(packed, ptr.data());
}
#[test]
fn new() {
let data = 0xdead_beef_u32;
let overflow = PackedPtr::new(&data, 4, AlignOnly);
assert!(matches!(overflow, Err(PackedPtrError::DataOverflow)));
let ok = PackedPtr::new(&data, 3, AlignOnly);
assert!(ok.is_ok());
}
#[test]
fn eq() {
let data = 0xdead_beef_u32;
let ptr = PackedPtr::new(&data, 2, AlignOnly).unwrap();
let ptr2 = PackedPtr::new(&data, 2, AlignOnly).unwrap();
assert_eq!(ptr, ptr2);
let ptr3 = PackedPtr::new(&data, 1, AlignOnly).unwrap();
assert_ne!(ptr, ptr3);
let data2 = 0xdead_beef_u32;
let ptr4 = PackedPtr::new(&data2, 2, AlignOnly).unwrap();
assert_ne!(ptr, ptr4);
}
}