ointers/lib.rs
1//! What do you call a pointer we stole the high bits off? An ointer.
2//!
3//! Ointers is a library for representing pointers where some bits have
4//! been stolen so that they may be used by the programmer for something
5//! else. In effect, it's a small amount of free storage
6//!
7//! Fully supports no_std, dependency-free.
8//!
9//! Ointers supports a handful of bit sources. It's up to you to determine
10//! when it is safe to use them.
11//!
12//! ## Alignment bits (const parameter A)
13//!
14//! If we know that a pointer's address will always be aligned to `A`
15//! bytes where A > 1, we can steal log2(A-1) bits. For an 8-byte
16//! aligned value, this provides a modest 3 bits.
17//!
18//! If you have values aligned to some larger width, you could get even
19//! more. It's common in parallel programming to pad data to a cache line
20//! by increasing its alignment requirements in order eliminate false
21//! sharing. Because a cache line on amd64 or aarch64 is effectively 128
22//! bytes thanks to prefetching, you can reclaim a respectable 7 extra
23//! bits.
24//!
25//! If your data is aligned wider still, the sky is the limit, but you
26//! could get an incredible 24 bits if you have 16MB-aligned data!
27//! Remember that the only alignment rust knows about is what is
28//! declared for the type, so you must create a newtype wrapper to
29//! take full advantage of large alignment sizes.
30//!
31//! | Bits | Min alignment |
32//! |------|---------------|
33//! | 1 | 2b |
34//! | 2 | 4b |
35//! | 3 | 8b |
36//! | 4 | 16b |
37//! | 5 | 32b |
38//! | 6 | 64b |
39//! | 7 | 128b |
40//! | 8 | 256b |
41//! | 9 | 512b |
42//! | 10 | 1k |
43//! | 11 | 2k |
44//! | 12 | 4k |
45//! | 13 | 8k |
46//! | 14 | 16k |
47//! | 15 | 32k |
48//! | 16 | 64k |
49//! | 17 | 128k |
50//! | 18 | 256k |
51//! | 19 | 512k |
52//! | 20 | 1m |
53//! | 21 | 2m |
54//! | 22 | 4m |
55//! | 23 | 8m |
56//! | 24 | 16m |
57//!
58//! Stealing bits from alignment is relatively innocuous, but we can only
59//! get the compiler to check it for you in dev mode as things stand in
60//! rust today.
61//!
62//! ## Sign bit (parameter S)
63//!
64//! The most commonly used operating systems arrange memory so that the
65//! high half of virtual memory space is reserved for the kernel and the
66//! low half is given to userspace.
67//!
68//! Looked at as a signed integer, this makes the kernel half of address
69//! space negative and the userspace half positive.
70//!
71//! Most programs do not deal with kernel addresses, thus giving us an
72//! extra bit to play with.
73//!
74//! We can also get this extra bit in kernel mode if we know we will not
75//! be dealing with userspace addresses. We do this by taking a pointer to
76//! a value on the stack and stealing its sign bit.
77//!
78//! If you know you will be dealing with userspace addresses in kernel
79//! space or kernel space addresses in userspace, or you are using or
80//! implementing a kernel which does not follow this convention, you must
81//! set `S` to `false`.
82//!
83//! The S bit is currently only tested with userspace pointers in
84//! userspace. While we think it should work more generally, we currently
85//! haven't got a test rig for other scenarios so we can't promise it does.
86//!
87//! ## Unused virtual address space (V)
88//!
89//! In 64-bit mode, address space is plentiful: nobody has 64 bits' worth
90//! of RAM and even if they did, their processor is unable to address it
91//! all. V is required to be 0 unless compiling for a 64bit target.
92//!
93//! The number of bits that may be safely stolen by this method depends
94//! upon the microarchitecture in question and the page table depth.
95//!
96//! For x86-64 and aarch64, the following sizes apply:
97//!
98//! | Bits | PT depth | Support |
99//! |------|----------|-----------------------------------|
100//! | 25 | 3 | aarch64 only, uncommon, opt-in |
101//! | 16 | 4 | most common default |
102//! | 7 | 5 | some intel only, uncommon, opt-in |
103//!
104//! If you are made of money and need more than 128TB virtual address
105//! space, you should limit yourself to 7 bits for V. Likewise if you know
106//! you'll be on 3-deep page tables, you can steal a whopping 25 bits. But
107//! you're probably limited to 16 bits in general.
108#![no_std]
109#![allow(unstable_name_collisions)]
110
111#[cfg(feature = "alloc")]
112extern crate alloc;
113#[cfg(feature = "alloc")]
114use alloc::boxed::Box;
115
116use core::hash::*;
117#[cfg(feature = "alloc")]
118use core::ops::{Deref, DerefMut};
119use core::ptr::NonNull;
120
121/// A pointer we stole the high bits off
122///
123/// T: type pointed to.
124/// A: number of bits to steal based on the alignment requirements of T.
125/// S: whether to steal the sign bit.
126/// V: number of bits to steal from unused virtual address space.
127
128#[derive(Debug)]
129#[repr(transparent)]
130pub struct Ointer<T: ?Sized, const A: u8, const S: bool, const V: u8> {
131 ptr: *mut T,
132}
133
134impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Hash for Ointer<T, A, S, V> {
135 fn hash<H: Hasher>(&self, state: &mut H) {
136 self.ptr.hash(state)
137 }
138}
139
140impl<T: ?Sized, const A: u8, const S: bool, const V: u8> PartialEq<Self> for Ointer<T, A, S, V> {
141 #![allow(
142 ambiguous_wide_pointer_comparisons,
143 reason = "mirroring the behaviour of rust ptrs"
144 )]
145 fn eq(&self, other: &Self) -> bool {
146 self.ptr == other.ptr
147 }
148}
149
150impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Eq for Ointer<T, A, S, V> {}
151
152impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Clone for Ointer<T, A, S, V> {
153 fn clone(&self) -> Self {
154 *self
155 }
156}
157
158impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Copy for Ointer<T, A, S, V> {}
159
160impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Ointer<T, A, S, V> {
161 /// Creates a new Ointer from a presumed legitimate pointer.
162 ///
163 /// # Safety
164 ///
165 /// * T's alignment must enable stealing A bits.
166 /// * The high bits (sign upwards) must match a stack pointer's high bits.
167 /// * If compiling for a 64bit arch, V must be at most 25.
168 /// * If compiling for a non-64bit arch, V must be 0.
169 ///
170 /// These invariants are checked with `debug_assert` only, hence
171 /// `unsafe`. The usual caveats of pointers apply.
172 pub unsafe fn new(ptr: *mut T) -> Self {
173 Ointer {
174 ptr: pack(ptr, A, S, V),
175 }
176 }
177
178 /// Constructor that enables stealing bits.
179 ///
180 /// # Safety
181 ///
182 /// Same as `new`
183 pub unsafe fn new_stealing(ptr: *mut T, bits: usize) -> Self {
184 let mask = asv_mask(A, S, V);
185 let ptr = ptr.with_addr((bits & mask) | (ptr.addr() & !mask));
186 Self { ptr }
187 }
188
189 /// Returns the stolen bits in the high pos.
190 pub fn stolen(self) -> usize {
191 self.ptr.addr() & asv_mask(A, S, V)
192 }
193
194 /// Takes a value from the high bits of the provided usize and
195 /// steals them from the ointer.
196 pub fn steal(self, bits: usize) -> Self {
197 let mask = asv_mask(A, S, V);
198 let ptr = self
199 .ptr
200 .with_addr((bits & mask) | (self.ptr.addr() & !mask));
201 Self { ptr }
202 }
203
204 /// Get the pointer without the stolen bits
205 pub fn as_ptr(self) -> *mut T {
206 unsafe { unpack(self.ptr, A, S, V) }
207 }
208
209 /// Direct access to the underlying data. The pointer it returns
210 /// may not be valid.
211 pub fn raw(self) -> usize {
212 self.ptr.expose_provenance()
213 }
214}
215
216/// A non-null pointer that we stole the high bits off.
217///
218/// T: type pointed to.
219/// V: number of bits to steal directly by masking them off.
220/// A: number of bits to steal based on the alignment requirements of T.
221#[derive(Debug)]
222#[repr(transparent)]
223pub struct NotNull<T: ?Sized, const A: u8, const S: bool, const V: u8>(NonNull<T>);
224
225impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Clone for NotNull<T, A, S, V> {
226 fn clone(&self) -> Self {
227 *self
228 }
229}
230
231impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Copy for NotNull<T, A, S, V> {}
232
233impl<T: ?Sized, const A: u8, const S: bool, const V: u8> PartialEq<Self> for NotNull<T, A, S, V> {
234 #![allow(
235 ambiguous_wide_pointer_comparisons,
236 reason = "mirroring the behaviour of NonNull<T>"
237 )]
238 fn eq(&self, other: &Self) -> bool {
239 self.0 == other.0
240 }
241}
242
243impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Eq for NotNull<T, A, S, V> {}
244
245impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Hash for NotNull<T, A, S, V> {
246 fn hash<H: Hasher>(&self, state: &mut H) {
247 self.0.hash(state)
248 }
249}
250
251impl<T: ?Sized, const A: u8, const S: bool, const V: u8> NotNull<T, A, S, V> {
252 /// Creates a new Ointer from a presumed legitimate pointer.
253 ///
254 /// # Safety
255 ///
256 /// * T's alignment must enable stealing A bits.
257 /// * The high bits (sign upwards) must match a stack pointer's high bits.
258 /// * If compiling for a 64bit arch, V must be at most 25.
259 /// * If compiling for a non-64bit arch, V must be 0.
260 ///
261 /// These invariants are checked with `debug_assert` only, hence
262 /// `unsafe`. The usual caveats of pointers apply.
263 pub unsafe fn new(ptr: NonNull<T>) -> Self {
264 let ptr = pack(ptr.as_ptr(), A, S, V);
265 NotNull(NonNull::new_unchecked(ptr))
266 }
267
268 /// Constructor that enables stealing bits.
269 ///
270 /// # Safety
271 ///
272 /// Same as `new`
273 pub unsafe fn new_stealing(ptr: NonNull<T>, bits: usize) -> Self {
274 let mask = asv_mask(A, S, V);
275 let ptr = ptr.as_ptr().map_addr(|addr| (bits & mask) | (addr & !mask));
276 NotNull(NonNull::new_unchecked(ptr))
277 }
278
279 /// Returns the stolen bits in the high pos.
280 pub fn stolen(self) -> usize {
281 self.0.as_ptr().addr() & asv_mask(A, S, V)
282 }
283
284 /// Takes a value from the high bits of the provided usize and
285 /// steals them from the ointer.
286 pub fn steal(self, bits: usize) -> Self {
287 let mask = asv_mask(A, S, V);
288 let bits = bits & mask;
289 let ptr = self.0.as_ptr();
290 let addr = ptr.addr() & !mask;
291 Self(unsafe { NonNull::new_unchecked(ptr.with_addr(addr | bits)) })
292 }
293
294 /// Get the pointer without the stolen bits
295 pub fn as_non_null(self) -> NonNull<T> {
296 unsafe { NonNull::new_unchecked(unpack(self.0.as_ptr(), A, S, V)) }
297 }
298
299 /// Direct access to the underlying data. The pointer it returns
300 /// may not be valid.
301 pub fn raw(self) -> usize {
302 self.0.as_ptr().expose_provenance()
303 }
304}
305
306/// A Box that we stole the high bits off.
307///
308/// T: type pointed to.
309/// V: number of bits to steal directly by masking them off.
310/// A: number of bits to steal based on the alignment requirements of T.
311#[derive(Debug)]
312#[repr(transparent)]
313#[cfg(feature = "alloc")]
314pub struct Ox<T: ?Sized, const A: u8, const S: bool, const V: u8>(NotNull<T, A, S, V>);
315
316#[cfg(feature = "alloc")]
317impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Clone for Ox<T, A, S, V>
318where
319 Box<T>: Clone,
320{
321 fn clone(&self) -> Self {
322 // Safety: even though we have mutable access to the ptr, we don't use it as it is ManuallyDrop
323 let old = core::mem::ManuallyDrop::new(unsafe { Box::from_raw(self.as_ptr()) });
324 let boxed = core::mem::ManuallyDrop::into_inner(old.clone());
325 // Safety: the stolen bits where taken from an existing Ox (thus safe)
326 unsafe { Ox::new_stealing(boxed, self.stolen()) }
327 }
328}
329
330#[cfg(feature = "alloc")]
331impl<T: ?Sized, const A: u8, const S: bool, const V: u8> PartialEq<Self> for Ox<T, A, S, V> {
332 fn eq(&self, other: &Self) -> bool {
333 self.0 == other.0
334 }
335}
336
337#[cfg(feature = "alloc")]
338impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Eq for Ox<T, A, S, V> {}
339
340#[cfg(feature = "alloc")]
341impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Hash for Ox<T, A, S, V> {
342 fn hash<H: Hasher>(&self, state: &mut H) {
343 self.0.hash(state)
344 }
345}
346
347#[cfg(feature = "alloc")]
348impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Ox<T, A, S, V> {
349 /// Creates a new Ox from a box.
350 ///
351 /// # Safety
352 ///
353 /// * T's alignment must enable stealing A bits.
354 /// * The high bits (sign upwards) must match a stack pointer's high bits.
355 /// * If compiling for a 64bit arch, V must be at most 25.
356 /// * If compiling for a non-64bit arch, V must be 0.
357 ///
358 /// These invariants are checked with `debug_assert` only, hence
359 /// `unsafe`. The usual caveats of pointers apply.
360 pub unsafe fn new(boxed: Box<T>) -> Self {
361 let ptr = Box::into_raw(boxed);
362 let ptr = pack(ptr, A, S, V);
363 Ox(NotNull::new(NonNull::new_unchecked(ptr)))
364 }
365
366 /// Constructor that enables stealing bits.
367 ///
368 /// # Safety
369 ///
370 /// Same as `new`
371 pub unsafe fn new_stealing(boxed: Box<T>, bits: usize) -> Self {
372 let mask = asv_mask(A, S, V);
373 let orig_ptr = Box::into_raw(boxed);
374 let ptr = orig_ptr.map_addr(|addr| (bits & mask) | (addr & !mask));
375 Self(NotNull::new(NonNull::new_unchecked(ptr)))
376 }
377
378 /// Returns the stolen bits in the high pos.
379 pub fn stolen(&self) -> usize {
380 self.0.stolen()
381 }
382
383 /// Takes a value from the high bits of the provided usize and
384 /// steals them from the ox.
385 pub fn steal(&mut self, bits: usize) {
386 let mask = asv_mask(A, S, V);
387 let bits = bits & mask;
388 let ptr = self.as_ptr().map_addr(|addr| (addr & !mask) | bits);
389 self.0 = unsafe { NotNull::new(NonNull::new_unchecked(ptr)) };
390 }
391
392 /// Get the box back without the stolen bits
393 pub fn into_box(self) -> Box<T> {
394 unsafe { Box::from_raw(unpack(self.as_ptr(), A, S, V)) }
395 }
396
397 /// Get the box back without the stolen bits
398 pub fn as_ptr(&self) -> *mut T {
399 self.0.as_non_null().as_ptr()
400 }
401
402 /// Direct access to the underlying data. The pointer it returns
403 /// may not be valid.
404 pub fn raw(&self) -> usize {
405 self.0.as_non_null().as_ptr().expose_provenance()
406 }
407}
408
409#[cfg(feature = "alloc")]
410impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Deref for Ox<T, A, S, V> {
411 type Target = T;
412 fn deref(&self) -> &T {
413 unsafe { self.0.as_non_null().as_ref() }
414 }
415}
416
417#[cfg(feature = "alloc")]
418impl<T: ?Sized, const A: u8, const S: bool, const V: u8> DerefMut for Ox<T, A, S, V> {
419 fn deref_mut(&mut self) -> &mut T {
420 unsafe { self.0.as_non_null().as_mut() }
421 }
422}
423
424#[cfg(feature = "alloc")]
425impl<T: ?Sized, const A: u8, const S: bool, const V: u8> Drop for Ox<T, A, S, V> {
426 fn drop(&mut self) {
427 drop(unsafe { Box::from_raw(self.0.as_non_null().as_ptr()) })
428 }
429}
430
431/// Packs a pointer into the bottom `sizeof(usize) - (a + s + v)` bits of a usize.
432///
433/// # Safety
434///
435/// * T's alignment must enable stealing A bits.
436/// * The high bits (sign upwards) must match a stack pointer's high bits.
437/// * If compiling for a 64bit arch, V must be at most 25.
438/// * If compiling for a non-64bit arch, V must be 0.
439///
440/// These invariants are checked with `debug_assert` only, hence
441/// `unsafe`. The usual caveats of pointers apply.
442pub unsafe fn pack<T: ?Sized>(ptr: *mut T, a: u8, s: bool, v: u8) -> *mut T {
443 let sv = asv_mask(0, s, v);
444 #[cfg(debug_assertions)]
445 {
446 if let Some(p) = ptr.as_ref() {
447 debug_assert!((1 << a) <= core::mem::align_of_val(p));
448 }
449 #[cfg(all(
450 not(target_pointer_width = "64"),
451 not(feature = "i_know_what_im_doing")
452 ))]
453 debug_assert!(v == 0);
454 debug_assert!(v <= 25);
455 // If S is set, the user has indicated they will never be
456 // dealing with foreign pointers, so we can check that
457 // too. We need only really check the sign bit because of
458 // canonicalisation, but it's actually cheaper to check
459 // all the high bits.
460 if s {
461 // We don't want to take account of A yet as the pointer
462 // is still in its undoctored state.
463 let ptr = ptr.addr();
464 let stack = (&ptr as *const usize).addr();
465 // the top bits should match
466 debug_assert!((sv & ptr) == (sv & stack));
467 }
468 }
469 ptr.with_addr((ptr.addr() & !sv) >> a as usize)
470}
471
472/// Turns the `sizeof(usize) - (a + s + v)` bits of a usize (as
473/// returned from `pack`) back into a pointer.
474///
475/// # Safety
476///
477/// The pointer must be of the correct type, otherwise you're
478/// basically unsafely casting the pointer.
479///
480/// You must use the same settings as you packed the pointer with. The
481/// pointer must be packed into the lower bits. Not strictly unsafe,
482/// but indicative of a bug in your program.
483pub unsafe fn unpack<T: ?Sized>(packed: *mut T, a: u8, s: bool, v: u8) -> *mut T {
484 // Mask off all the stolen bits to get the pointer data.
485 let asv = asv_mask(a, s, v);
486 let masked = packed.addr() & !asv;
487 // Restore the empty alignment bits
488 let base = masked << a;
489 if s {
490 // Copy the top bits of a stack pointer
491 let sv = asv_mask(0, s, v);
492 let base = base & !sv;
493 let stack = (&base as *const usize).addr() & sv;
494 packed.with_addr(stack | base)
495 } else {
496 // We need to extend the sign bit.
497 packed.with_addr((((base << v as usize) as isize) >> v as usize) as usize)
498 }
499}
500
501/// Produces a mask where the stolen bits (at the top) are set
502pub const fn asv_mask(a: u8, s: bool, v: u8) -> usize {
503 mask(a + s as u8 + v)
504}
505
506/// Produces a mask where the stolen bits (at the top) are set
507pub const fn mask(bits: u8) -> usize {
508 (isize::MIN >> (max(bits as usize, 1) - 1)) as usize
509}
510
511// core::cmp::max and usize::max aren't const fns
512const fn max(x: usize, y: usize) -> usize {
513 if x <= y {
514 y
515 } else {
516 x
517 }
518}