ps_alloc/header.rs
1use std::alloc::{Layout, LayoutError};
2
3use crate::{
4 error::ValidationError,
5 marker::{MARKER_FREE, MARKER_USED},
6 AllocationError,
7};
8
9/// Metadata header prefixed to every allocation.
10///
11/// Stores the allocation state via `marker` (`MARKER_USED` or `MARKER_FREE`) and the
12/// total allocation size in bytes (including this header and alignment padding) in `size`.
13///
14/// Aligned to 16 bytes to guarantee all allocations are aligned to 16 bytes.
15#[repr(align(16))]
16pub struct AllocationHeader {
17 marker: [u8; 8],
18 size: usize,
19}
20
21impl AllocationHeader {
22 /// Constructs a header for a used allocation of `size` total bytes.
23 #[inline]
24 pub const fn new(size: usize) -> Self {
25 Self {
26 marker: MARKER_USED,
27 size,
28 }
29 }
30
31 /// Returns the total allocation size in bytes stored in this header.
32 #[inline]
33 pub const fn get_size(&self) -> usize {
34 self.size
35 }
36
37 /// Checks whether this header is marked as free (deallocated).
38 ///
39 /// Marker checks are best-effort debugging aids.
40 #[inline]
41 pub fn is_marked_as_free(&self) -> bool {
42 self.marker == MARKER_FREE
43 }
44
45 /// Checks whether this header is marked as used (allocated).
46 ///
47 /// Marker checks are best-effort debugging aids.
48 #[inline]
49 pub fn is_marked_as_used(&self) -> bool {
50 self.marker == MARKER_USED
51 }
52
53 /// Marks this header as free (deallocated).
54 #[inline]
55 pub const fn mark_as_free(&mut self) {
56 self.marker = MARKER_FREE;
57 }
58}
59
60/// The size in bytes of the hidden header prefixed to every allocation; also the
61/// alignment of every allocation produced by this crate.
62pub const HEADER_SIZE: usize = std::mem::size_of::<AllocationHeader>();
63
64/// The alignment of every allocation produced by this crate.
65pub(crate) const ALIGN: usize = std::mem::align_of::<AllocationHeader>();
66
67// The user pointer is offset from the allocation's base by `HEADER_SIZE`, so the two
68// constants must be equal for the user pointer to be `ALIGN`-aligned.
69const _: () = assert!(HEADER_SIZE == ALIGN);
70
71/// Computes the total allocation size for a user buffer of `size` bytes: the header plus
72/// the buffer, rounded up to a multiple of [`ALIGN`].
73///
74/// # Errors
75/// - `Err(ArithmeticError)` on integer overflow.
76pub(crate) fn total_size(size: usize) -> Result<usize, AllocationError> {
77 size.checked_add(HEADER_SIZE)
78 .ok_or(AllocationError::ArithmeticError)?
79 .checked_next_multiple_of(ALIGN)
80 .ok_or(AllocationError::ArithmeticError)
81}
82
83/// Computes the [`Layout`] of an allocation of `total` bytes.
84///
85/// # Errors
86/// - `Err(LayoutError)` if `total` overflows [`isize::MAX`] when rounded up to [`ALIGN`].
87pub(crate) fn layout_of(total: usize) -> Result<Layout, LayoutError> {
88 Layout::from_size_align(total, ALIGN)
89}
90
91/// Writes a used-allocation header of `total` bytes at `base` and returns the user
92/// pointer at `base + HEADER_SIZE`.
93///
94/// # Safety
95/// `base` must be non-null, [`ALIGN`]-aligned, and valid for writes of at least `total`
96/// bytes, where `total` is at least [`HEADER_SIZE`].
97pub(crate) unsafe fn initialize_block(base: *mut u8, total: usize) -> *mut u8 {
98 // SAFETY: `base` is valid for writes of at least `HEADER_SIZE` bytes and is
99 // sufficiently aligned, per this function's contract.
100 unsafe {
101 base.cast::<AllocationHeader>()
102 .write(AllocationHeader::new(total))
103 };
104
105 // SAFETY: the allocation is at least `HEADER_SIZE` bytes, so the offset stays in bounds.
106 unsafe { base.add(HEADER_SIZE) }
107}
108
109/// Validates a non-null user pointer and returns the pointer to its allocation header.
110///
111/// # Errors
112/// - `Err(ImproperAlignment)` if `ptr` is not [`ALIGN`]-aligned.
113/// - `Err(MarkerFree)` if the header is marked as free.
114/// - `Err(MarkerCorrupted)` if the header matches neither marker.
115///
116/// # Safety
117/// `ptr` must be a non-null pointer produced by this crate's allocation functions.
118/// Unless `ptr` is misaligned, this function dereferences the header below it; passing
119/// any other pointer causes undefined behaviour.
120pub(crate) unsafe fn validate(ptr: *mut u8) -> Result<*mut AllocationHeader, ValidationError> {
121 // SAFETY: the caller guarantees `ptr` was allocated by [`crate::alloc`], so the
122 // allocation begins `HEADER_SIZE` bytes below it and the subtraction stays in bounds.
123 let header_ptr: *mut AllocationHeader = unsafe { ptr.sub(HEADER_SIZE) }.cast();
124
125 if !header_ptr.is_aligned() {
126 return Err(ValidationError::ImproperAlignment);
127 }
128
129 // SAFETY: the caller guarantees `ptr` was allocated by [`crate::alloc`], so
130 // `header_ptr` points to a live, initialized header.
131 if unsafe { &*header_ptr }.is_marked_as_free() {
132 return Err(ValidationError::MarkerFree);
133 }
134
135 // SAFETY: as above.
136 if !unsafe { &*header_ptr }.is_marked_as_used() {
137 return Err(ValidationError::MarkerCorrupted);
138 }
139
140 Ok(header_ptr)
141}