abi_definitions/memory/
allocated.rs

1use core::{alloc::Layout, ptr::NonNull};
2
3use alloc::slice;
4
5use crate::CompactLayout;
6
7pub struct Allocated {
8    pointer: NonNull<u8>,
9    padding: u8,
10}
11
12impl Allocated {
13    const HEADER_SIZE: usize = size_of::<CompactLayout>();
14
15    pub const fn get_padding(alignment: usize) -> usize {
16        let misalignment = Self::HEADER_SIZE % alignment;
17        if misalignment == 0 {
18            0
19        } else {
20            alignment - misalignment
21        }
22    }
23
24    pub fn get_layout_for_allocation(size: usize, alignment: usize) -> Layout {
25        // Calculate padding needed to align user data after header
26        let padding = Self::get_padding(alignment);
27
28        let offset = Self::HEADER_SIZE + padding;
29        let total_size = offset + size;
30
31        // The allocation must be aligned to at least the user data alignment
32        // to ensure that base_ptr + offset is properly aligned
33        Layout::from_size_align(total_size, alignment).expect("Failed to create allocation layout")
34    }
35
36    fn new(pointer: *mut u8, padding: usize) -> Option<Self> {
37        Some(Self {
38            pointer: NonNull::new(pointer)?,
39            padding: padding as u8,
40        })
41    }
42
43    pub fn from_layout(pointer: *mut u8, layout: &Layout) -> Option<Self> {
44        let padding = Self::get_padding(layout.align());
45        let allocated = Self::new(pointer, padding)?;
46
47        // Store the layout before the user pointer
48        allocated.set_layout(layout);
49
50        Some(allocated)
51    }
52
53    /// # Safety
54    ///
55    /// The provided user pointer must have been obtained from a previous allocation
56    /// that stored a CompactLayout before the user pointer.
57    pub unsafe fn from_user_pointer(user_pointer: *mut u8) -> Option<Self> {
58        if user_pointer.is_null() {
59            return None;
60        }
61
62        // Read the layout stored before the user pointer
63        let layout_ptr = unsafe { user_pointer.sub(size_of::<CompactLayout>()) };
64        let layout_bytes = unsafe { slice::from_raw_parts(layout_ptr, size_of::<CompactLayout>()) };
65        let layout = CompactLayout::from_le_bytes(layout_bytes.try_into().ok()?)?;
66
67        let padding = Self::get_padding(layout.get_alignment());
68        let total_header_size = Self::HEADER_SIZE + padding;
69        let base_pointer = unsafe { user_pointer.sub(total_header_size) };
70
71        Some(Self {
72            pointer: NonNull::new(base_pointer)?,
73            padding: padding as u8,
74        })
75    }
76
77    pub fn get_layout(&self) -> Option<Layout> {
78        let layout_pointer = unsafe { self.pointer.as_ptr().add(self.padding as usize) };
79
80        let bytes = unsafe { slice::from_raw_parts(layout_pointer, size_of::<CompactLayout>()) };
81
82        let layout = CompactLayout::from_le_bytes(
83            bytes
84                .try_into()
85                .expect("Failed to read CompactLayout from pointer"),
86        )?;
87
88        layout.into_layout()
89    }
90
91    pub fn erase_layout(&self) {
92        let layout_pointer = unsafe { self.pointer.as_ptr().add(self.padding as usize) };
93
94        let zero_bytes = [0u8; size_of::<CompactLayout>()];
95
96        unsafe {
97            core::ptr::copy_nonoverlapping(
98                zero_bytes.as_ptr(),
99                layout_pointer,
100                size_of::<CompactLayout>(),
101            );
102        }
103    }
104
105    pub fn set_layout(&self, layout: &Layout) {
106        let bytes = CompactLayout::from_layout(layout)
107            .expect("Failed to convert Layout to CompactLayout")
108            .to_le_bytes();
109
110        let layout_pointer = unsafe { self.pointer.as_ptr().add(self.padding as usize) };
111
112        unsafe {
113            core::ptr::copy_nonoverlapping(
114                bytes.as_ptr(),
115                layout_pointer,
116                size_of::<CompactLayout>(),
117            );
118        }
119    }
120
121    pub fn get_base_pointer(&self) -> *mut u8 {
122        self.pointer.as_ptr()
123    }
124
125    pub fn get_user_pointer(&self) -> *mut u8 {
126        let offset = self.padding as usize + Self::HEADER_SIZE;
127
128        unsafe { self.pointer.as_ptr().add(offset) }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use core::alloc::Layout;
136
137    #[test]
138    fn test_get_layout_for_allocation_basic() {
139        let layout = Layout::from_size_align(64, 8).unwrap();
140        let alloc_layout = Allocated::get_layout_for_allocation(layout.size(), layout.align());
141
142        let padding = Allocated::get_padding(layout.align());
143        let offset = Allocated::HEADER_SIZE + padding;
144        assert!(offset >= Allocated::HEADER_SIZE);
145        assert!(alloc_layout.size() >= layout.size() + Allocated::HEADER_SIZE);
146        assert_eq!(alloc_layout.align(), layout.align());
147    }
148
149    #[test]
150    fn test_get_layout_for_allocation_alignment() {
151        let layout = Layout::from_size_align(32, 16).unwrap();
152        let padding = Allocated::get_padding(layout.align());
153
154        // User data should be properly aligned
155        let offset = Allocated::HEADER_SIZE + padding;
156        assert_eq!(offset % layout.align(), 0);
157    }
158
159    #[test]
160    fn test_new_with_null_pointer() {
161        let result = Allocated::new(core::ptr::null_mut(), 0);
162        assert!(result.is_none());
163    }
164
165    #[test]
166    fn test_new_with_valid_pointer() {
167        let mut data = [0u8; 64];
168        let result = Allocated::new(data.as_mut_ptr(), 8);
169        assert!(result.is_some());
170    }
171
172    #[test]
173    fn test_get_user_pointer_offset() {
174        let mut data = [0u8; 64];
175        let base_ptr = data.as_mut_ptr();
176        let padding = 16usize;
177
178        let allocated = Allocated::new(base_ptr, padding).unwrap();
179        let user_ptr = allocated.get_user_pointer();
180
181        assert_eq!(
182            user_ptr as usize,
183            base_ptr as usize + padding + Allocated::HEADER_SIZE
184        );
185    }
186
187    #[test]
188    fn test_layout_round_trip() {
189        let mut data = [0u8; 64];
190        let allocated = Allocated::new(data.as_mut_ptr(), 8).unwrap();
191
192        let original_layout = Layout::from_size_align(128, 16).unwrap();
193        allocated.set_layout(&original_layout);
194        let retrieved_layout = allocated.get_layout().unwrap();
195
196        assert_eq!(original_layout, retrieved_layout);
197    }
198
199    #[test]
200    fn test_multiple_alignment_values() {
201        for align in [1, 2, 4, 8, 16, 32, 64, 128] {
202            let padding = Allocated::get_padding(align);
203            let offset = Allocated::HEADER_SIZE + padding;
204            assert_eq!(offset % align, 0, "Failed for alignment {}", align);
205        }
206    }
207
208    #[test]
209    fn test_zero_size_layout() {
210        let layout = Layout::from_size_align(0, 1).unwrap();
211        let alloc_layout = Allocated::get_layout_for_allocation(layout.size(), layout.align());
212        let padding = Allocated::get_padding(layout.align());
213
214        let offset = Allocated::HEADER_SIZE + padding;
215        assert!(offset >= Allocated::HEADER_SIZE);
216        assert_eq!(alloc_layout.size(), offset);
217    }
218
219    #[test]
220    fn test_from_user_pointer_round_trip() {
221        // Test that we can recover the Allocated struct from a user pointer
222        let mut data = [0u8; 128];
223        let base_ptr = data.as_mut_ptr();
224
225        let layout = Layout::from_size_align(64, 16).unwrap();
226        let allocated = Allocated::from_layout(base_ptr, &layout).unwrap();
227
228        let user_ptr = allocated.get_user_pointer();
229        let recovered = unsafe { Allocated::from_user_pointer(user_ptr).unwrap() };
230
231        // Verify the recovered struct matches the original
232        assert_eq!(recovered.pointer, allocated.pointer);
233        assert_eq!(recovered.padding, allocated.padding);
234        assert_eq!(recovered.get_layout().unwrap(), layout);
235    }
236
237    #[test]
238    fn test_user_pointer_alignment() {
239        // Verify that user pointers are correctly aligned when base pointer is aligned
240        use alloc::alloc::{alloc, dealloc};
241
242        for align in [1, 2, 4, 8, 16, 32, 64, 128] {
243            let layout = Layout::from_size_align(256, align).unwrap();
244            let base_ptr = unsafe { alloc(layout) };
245            assert!(!base_ptr.is_null(), "Allocation failed");
246
247            // Now test with the properly aligned base pointer
248            let user_layout = Layout::from_size_align(32, align).unwrap();
249            let padding = Allocated::get_padding(user_layout.align());
250
251            let allocated = Allocated::new(base_ptr, padding).unwrap();
252            let user_ptr = allocated.get_user_pointer();
253
254            // User pointer should be aligned to the requested alignment
255            assert_eq!(
256                user_ptr as usize % align,
257                0,
258                "User pointer not aligned to {} bytes",
259                align
260            );
261
262            unsafe { dealloc(base_ptr, layout) };
263        }
264    }
265
266    #[test]
267    fn test_layout_stored_before_user_pointer() {
268        // Verify that the layout is stored immediately before the user pointer
269        let mut data = [0u8; 128];
270        let base_ptr = data.as_mut_ptr();
271
272        let layout = Layout::from_size_align(64, 8).unwrap();
273        let allocated = Allocated::from_layout(base_ptr, &layout).unwrap();
274        let user_ptr = allocated.get_user_pointer();
275
276        // Read layout directly from memory just before user pointer
277        let layout_ptr = unsafe { user_ptr.sub(Allocated::HEADER_SIZE) };
278        let layout_bytes =
279            unsafe { core::slice::from_raw_parts(layout_ptr, size_of::<CompactLayout>()) };
280        let read_layout = CompactLayout::from_le_bytes(layout_bytes.try_into().unwrap());
281        let compact_layout = CompactLayout::from_layout(&layout).unwrap();
282
283        assert_eq!(read_layout.unwrap(), compact_layout);
284    }
285
286    #[test]
287    fn test_padding_calculation_correctness() {
288        // Verify padding calculation ensures proper alignment
289        for align in [1, 2, 4, 8, 16, 32, 64, 128] {
290            let padding = Allocated::get_padding(align);
291            let user_data_offset = Allocated::HEADER_SIZE + padding;
292
293            assert_eq!(
294                user_data_offset % align,
295                0,
296                "Padding calculation failed for alignment {}",
297                align
298            );
299        }
300    }
301
302    #[test]
303    fn test_allocation_layout_size() {
304        // Verify that allocation layout has enough space for header + padding + data
305        let layout = Layout::from_size_align(100, 32).unwrap();
306        let alloc_layout = Allocated::get_layout_for_allocation(layout.size(), layout.align());
307        let padding = Allocated::get_padding(layout.align());
308
309        let offset = Allocated::HEADER_SIZE + padding;
310        assert!(alloc_layout.size() >= Allocated::HEADER_SIZE + layout.size());
311        assert_eq!(alloc_layout.size(), offset + layout.size());
312    }
313}