flize/tag.rs
1use core::mem;
2use generic_array::{
3 typenum::{UTerm, Unsigned},
4 ArrayLength, GenericArray,
5};
6
7pub enum TagPosition {
8 Lo,
9 Hi,
10}
11
12impl TagPosition {
13 /// Calculates the start bit offset of the tag depending on the position and type.
14 fn to_skip<T: Tag>(&self) -> usize {
15 match self {
16 // low tags always start at 0
17 TagPosition::Lo => 0,
18
19 // high tags occupy the highest bits so the start offset is the max index minus the size
20 TagPosition::Hi => {
21 let usize_bits = mem::size_of::<usize>() * 8;
22 usize_bits - <T::Size as Unsigned>::to_usize()
23 }
24 }
25 }
26}
27
28/// Zeroes all the tag bits.
29pub fn strip<T1: Tag, T2: Tag>(data: usize) -> usize {
30 // mask for zeroing the low tag
31 let mask1: usize = core::usize::MAX >> <T1::Size as Unsigned>::to_usize();
32
33 // mask for zeroing the high tag
34 let mask2: usize = core::usize::MAX << <T2::Size as Unsigned>::to_usize();
35
36 // apply the masks with an AND to zero the bits
37 data & mask1 & mask2
38}
39
40/// Read the bits of a tag a a certain position.
41pub fn read_tag<T: Tag>(data: usize, position: TagPosition) -> GenericArray<bool, T::Size> {
42 let to_skip = position.to_skip::<T>();
43 let mut array = GenericArray::default();
44
45 array
46 .iter_mut()
47 .enumerate()
48 .skip(to_skip)
49 .for_each(|(index, bit)| *bit = ((data >> index) & 1) == 1);
50
51 array
52}
53
54/// Set the bits of a tag at a certain position.
55pub fn set_tag<T: Tag>(
56 mut data: usize,
57 bits: GenericArray<bool, T::Size>,
58 position: TagPosition,
59) -> usize {
60 let to_skip = position.to_skip::<T>();
61
62 bits.iter()
63 .enumerate()
64 .skip(to_skip)
65 .for_each(|(index, bit)| {
66 let value = if *bit { 1 } else { 0 };
67 data = (data & !(1 << index)) | (value << index);
68 });
69
70 data
71}
72
73/// The `Tag` trait represents any struct that can be serialized
74/// and packed into the unused bits of a pointer producing
75/// a so called "tagged" pointer.
76/// The amount of bits available are variable and the amount
77/// you can use depends on whether the tag is in in the low or high position.
78///
79/// In low position you can use as many bits as must be zero due to
80/// alignment. If you don't know the alignment of your pointer you can assume it is
81/// that of the value it is pointing to. The amount of available bits in the low
82/// position is the binary logarithm of the alignment in bytes.
83///
84/// In high position the number of available bits is determined by your compilation target.
85/// On 32 bit architectures this number shall be assumed to be equal to 0.
86/// On x86_64 with 4 level paging the number of available bits is 16 and with level
87/// 5 paging it is 8 bits. On 64 bit ARM without pointer authentication you also have 16
88/// available bits. With pointer authentication you can only reasonably assume you have 0 available
89/// bits unless you know otherwise for your compiler. On all other architectures assume you have
90/// 0 available bits unless you know otherwise.
91pub trait Tag: Copy {
92 /// The size in bits of the tag.
93 type Size: ArrayLength<bool>;
94
95 /// Deserialize an array of bits into the tag.
96 fn deserialize(bits: GenericArray<bool, Self::Size>) -> Self;
97
98 /// Serialize the tag to an array of bits.
99 fn serialize(self) -> GenericArray<bool, Self::Size>;
100}
101
102/// This tag is a placeholder type that has a size of 0 and stores no state.
103/// If you don't have any tag with information you want to store, this is the default.
104#[derive(Debug, Clone, Copy)]
105pub struct NullTag;
106
107impl Tag for NullTag {
108 type Size = UTerm;
109
110 fn deserialize(_bits: GenericArray<bool, Self::Size>) -> Self {
111 Self
112 }
113
114 fn serialize(self) -> GenericArray<bool, Self::Size> {
115 GenericArray::default()
116 }
117}