Skip to main content

kalloc/
allocator.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use core::alloc::Layout;
8use core::ptr::NonNull;
9
10/// Error type for allocation failures.
11#[derive(Debug, Eq, PartialEq)]
12pub struct AllocError;
13
14impl core::fmt::Display for AllocError {
15    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
16        f.write_str("allocation failure")
17    }
18}
19
20impl core::error::Error for AllocError {}
21
22/// Trait for allocators used by Box and other collections.
23///
24/// This trait mirrors the `core::alloc::Allocator` trait,
25/// returning `Result<NonNull<[u8]>, AllocError>` to provide the actual
26/// size allocated.
27///
28/// Implementations of this trait must tolerate being passed zero-sized layouts.
29/// For zero-sized allocations, implementations should return a dangling pointer
30/// with the appropriate alignment, and `deallocate` should be a no-op for such pointers.
31pub trait Allocator: Clone {
32    /// Allocates memory as described by the given `layout`.
33    ///
34    /// If the layout has a size of zero, this method returns a dangling pointer.
35    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
36
37    /// # Safety
38    ///
39    /// - The pointer must have been allocated by `allocate` with the same layout.
40    ///
41    /// If the layout has a size of zero, this method is a no-op.
42    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
43
44    /// # Safety
45    ///
46    /// - `ptr` must denote a block of memory currently allocated via this allocator.
47    /// - `old_layout` must fit that block of memory (The `new_layout` argument need not fit it.).
48    /// - `new_layout.size()` must be greater than or equal to `old_layout.size()`.
49    ///
50    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
51    ///
52    /// If the new layout has a size of zero, this method returns a dangling pointer.
53    unsafe fn grow(
54        &self,
55        ptr: NonNull<u8>,
56        old_layout: Layout,
57        new_layout: Layout,
58    ) -> Result<NonNull<[u8]>, AllocError>;
59
60    /// # Safety
61    ///
62    /// - `ptr` must denote a block of memory currently allocated via this allocator.
63    /// - `old_layout` must fit that block of memory (The `new_layout` argument need not fit it.).
64    /// - `new_layout.size()` must be less than or equal to `old_layout.size()`.
65    ///
66    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
67    ///
68    /// If the new layout has a size of zero, this method deallocates the memory and returns a dangling pointer.
69    unsafe fn shrink(
70        &self,
71        ptr: NonNull<u8>,
72        old_layout: Layout,
73        new_layout: Layout,
74    ) -> Result<NonNull<[u8]>, AllocError>;
75
76    /// Allocates zeroed memory as described by the given `layout`.
77    ///
78    /// If the layout has a size of zero, this method returns a dangling pointer.
79    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
80}
81
82/// Default allocator that uses the global allocator (userspace) or kernel allocator.
83#[derive(Clone, Default)]
84pub struct DefaultAllocator;
85
86impl Allocator for DefaultAllocator {
87    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
88        if layout.size() == 0 {
89            return Ok(NonNull::from_ref(&[]));
90        }
91        let ptr = unsafe { crate::alloc(layout).ok_or(AllocError)? };
92        Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
93    }
94
95    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
96        if layout.size() == 0 {
97            return;
98        }
99        unsafe { crate::dealloc(ptr.as_ptr(), layout) }
100    }
101
102    unsafe fn grow(
103        &self,
104        ptr: NonNull<u8>,
105        old_layout: Layout,
106        new_layout: Layout,
107    ) -> Result<NonNull<[u8]>, AllocError> {
108        assert!(new_layout.size() >= old_layout.size());
109        if new_layout.size() == 0 {
110            return Ok(NonNull::from_ref(&[]));
111        }
112        if old_layout.size() == 0 {
113            return self.allocate(new_layout);
114        }
115        let ptr = unsafe {
116            crate::realloc(ptr.as_ptr(), old_layout, new_layout.size()).ok_or(AllocError)?
117        };
118        Ok(NonNull::slice_from_raw_parts(ptr, new_layout.size()))
119    }
120
121    unsafe fn shrink(
122        &self,
123        ptr: NonNull<u8>,
124        old_layout: Layout,
125        new_layout: Layout,
126    ) -> Result<NonNull<[u8]>, AllocError> {
127        assert!(new_layout.size() <= old_layout.size());
128        if new_layout.size() == 0 {
129            unsafe { self.deallocate(ptr, old_layout) };
130            return Ok(NonNull::from_ref(&[]));
131        }
132        if old_layout.size() == 0 {
133            return Ok(NonNull::from_ref(&[]));
134        }
135        let ptr = unsafe {
136            crate::realloc(ptr.as_ptr(), old_layout, new_layout.size()).ok_or(AllocError)?
137        };
138        Ok(NonNull::slice_from_raw_parts(ptr, new_layout.size()))
139    }
140
141    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
142        if layout.size() == 0 {
143            return Ok(NonNull::from_ref(&[]));
144        }
145        let ptr = unsafe { crate::alloc_zeroed(layout).ok_or(AllocError)? };
146        Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_default_allocator() {
156        let layout = Layout::from_size_align(10, 1).unwrap();
157        let ptr = DefaultAllocator::default().allocate(layout).unwrap();
158        unsafe {
159            DefaultAllocator::default().deallocate(ptr.cast::<u8>(), layout);
160        }
161    }
162
163    #[test]
164    fn test_default_allocator_grow_shrink() {
165        let layout = Layout::from_size_align(10, 1).unwrap();
166        let ptr = DefaultAllocator::default().allocate(layout).unwrap();
167
168        let new_layout = Layout::from_size_align(20, 1).unwrap();
169        let grown_ptr = unsafe {
170            DefaultAllocator::default().grow(ptr.cast::<u8>(), layout, new_layout).unwrap()
171        };
172        assert!(grown_ptr.len() >= 20);
173
174        let shrunk_layout = Layout::from_size_align(5, 1).unwrap();
175        let shrunk_ptr = unsafe {
176            DefaultAllocator::default()
177                .shrink(grown_ptr.cast::<u8>(), new_layout, shrunk_layout)
178                .unwrap()
179        };
180        assert!(shrunk_ptr.len() >= 5);
181
182        unsafe {
183            DefaultAllocator::default().deallocate(shrunk_ptr.cast::<u8>(), shrunk_layout);
184        }
185    }
186
187    #[test]
188    fn test_default_allocator_zeroed() {
189        let layout = Layout::from_size_align(10, 1).unwrap();
190        let ptr = DefaultAllocator::default().allocate_zeroed(layout).unwrap();
191
192        // Verify it is zeroed!
193        let slice = unsafe { ptr.as_ref() };
194        for &b in slice {
195            assert_eq!(b, 0);
196        }
197
198        unsafe {
199            DefaultAllocator::default().deallocate(ptr.cast::<u8>(), layout);
200        }
201    }
202
203    #[test]
204    fn test_default_allocator_zero_sized() {
205        let allocator = DefaultAllocator::default();
206
207        let layout0 = Layout::from_size_align(0, 1).unwrap();
208        let ptr0 = allocator.allocate(layout0).unwrap();
209        assert_eq!(ptr0.len(), 0);
210
211        let ptr0_zeroed = allocator.allocate_zeroed(layout0).unwrap();
212        assert_eq!(ptr0_zeroed.len(), 0);
213
214        unsafe {
215            allocator.deallocate(ptr0.cast::<u8>(), layout0);
216            allocator.deallocate(ptr0_zeroed.cast::<u8>(), layout0);
217        }
218
219        let grown0 = unsafe { allocator.grow(ptr0.cast::<u8>(), layout0, layout0).unwrap() };
220        assert_eq!(grown0.len(), 0);
221
222        let layout10 = Layout::from_size_align(10, 1).unwrap();
223        let grown10 = unsafe { allocator.grow(ptr0.cast::<u8>(), layout0, layout10).unwrap() };
224        assert!(grown10.len() >= 10);
225
226        let shrunk0 = unsafe { allocator.shrink(grown10.cast::<u8>(), layout10, layout0).unwrap() };
227        assert_eq!(shrunk0.len(), 0);
228
229        let shrunk0_again =
230            unsafe { allocator.shrink(ptr0.cast::<u8>(), layout0, layout0).unwrap() };
231        assert_eq!(shrunk0_again.len(), 0);
232    }
233}