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
22impl From<core::convert::Infallible> for AllocError {
23    fn from(never: core::convert::Infallible) -> Self {
24        match never {}
25    }
26}
27
28/// Trait for allocators used by Box and other collections.
29///
30/// This trait mirrors the `core::alloc::Allocator` trait,
31/// returning `Result<NonNull<[u8]>, AllocError>` to provide the actual
32/// size allocated.
33///
34/// Implementations of this trait must tolerate being passed zero-sized layouts.
35/// For zero-sized allocations, implementations should return a dangling pointer
36/// with the appropriate alignment, and `deallocate` should be a no-op for such pointers.
37pub trait Allocator: Clone {
38    /// Allocates memory as described by the given `layout`.
39    ///
40    /// If the layout has a size of zero, this method returns a dangling pointer.
41    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
42
43    /// # Safety
44    ///
45    /// - The pointer must have been allocated by `allocate` with the same layout.
46    ///
47    /// If the layout has a size of zero, this method is a no-op.
48    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
49
50    /// # Safety
51    ///
52    /// - `ptr` must denote a block of memory currently allocated via this allocator.
53    /// - `old_layout` must fit that block of memory (The `new_layout` argument need not fit it.).
54    /// - `new_layout.size()` must be greater than or equal to `old_layout.size()`.
55    ///
56    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
57    ///
58    /// If the new layout has a size of zero, this method returns a dangling pointer.
59    unsafe fn grow(
60        &self,
61        ptr: NonNull<u8>,
62        old_layout: Layout,
63        new_layout: Layout,
64    ) -> Result<NonNull<[u8]>, AllocError>;
65
66    /// # Safety
67    ///
68    /// - `ptr` must denote a block of memory currently allocated via this allocator.
69    /// - `old_layout` must fit that block of memory (The `new_layout` argument need not fit it.).
70    /// - `new_layout.size()` must be less than or equal to `old_layout.size()`.
71    ///
72    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
73    ///
74    /// If the new layout has a size of zero, this method deallocates the memory and returns a dangling pointer.
75    unsafe fn shrink(
76        &self,
77        ptr: NonNull<u8>,
78        old_layout: Layout,
79        new_layout: Layout,
80    ) -> Result<NonNull<[u8]>, AllocError>;
81
82    /// Allocates zeroed memory as described by the given `layout`.
83    ///
84    /// If the layout has a size of zero, this method returns a dangling pointer.
85    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
86}
87
88/// Default allocator that uses the global allocator (userspace) or kernel allocator.
89#[derive(Clone, Default)]
90pub struct DefaultAllocator;
91
92impl Allocator for DefaultAllocator {
93    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
94        if layout.size() == 0 {
95            return Ok(NonNull::from_ref(&[]));
96        }
97        let ptr = unsafe { crate::alloc(layout).ok_or(AllocError)? };
98        Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
99    }
100
101    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
102        if layout.size() == 0 {
103            return;
104        }
105        unsafe { crate::dealloc(ptr.as_ptr(), layout) }
106    }
107
108    unsafe fn grow(
109        &self,
110        ptr: NonNull<u8>,
111        old_layout: Layout,
112        new_layout: Layout,
113    ) -> Result<NonNull<[u8]>, AllocError> {
114        assert!(new_layout.size() >= old_layout.size());
115        if new_layout.size() == 0 {
116            return Ok(NonNull::from_ref(&[]));
117        }
118        if old_layout.size() == 0 {
119            return self.allocate(new_layout);
120        }
121        let ptr = unsafe {
122            crate::realloc(ptr.as_ptr(), old_layout, new_layout.size()).ok_or(AllocError)?
123        };
124        Ok(NonNull::slice_from_raw_parts(ptr, new_layout.size()))
125    }
126
127    unsafe fn shrink(
128        &self,
129        ptr: NonNull<u8>,
130        old_layout: Layout,
131        new_layout: Layout,
132    ) -> Result<NonNull<[u8]>, AllocError> {
133        assert!(new_layout.size() <= old_layout.size());
134        if new_layout.size() == 0 {
135            unsafe { self.deallocate(ptr, old_layout) };
136            return Ok(NonNull::from_ref(&[]));
137        }
138        if old_layout.size() == 0 {
139            return Ok(NonNull::from_ref(&[]));
140        }
141        let ptr = unsafe {
142            crate::realloc(ptr.as_ptr(), old_layout, new_layout.size()).ok_or(AllocError)?
143        };
144        Ok(NonNull::slice_from_raw_parts(ptr, new_layout.size()))
145    }
146
147    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
148        if layout.size() == 0 {
149            return Ok(NonNull::from_ref(&[]));
150        }
151        let ptr = unsafe { crate::alloc_zeroed(layout).ok_or(AllocError)? };
152        Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
153    }
154}
155
156/// An allocator that does not perform any actual allocation or deallocation.
157///
158/// This is useful when constructing Box and other dynamically allocated collections from raw
159/// pointers to pre-existing memory that is owned and managed elsewhere (e.g., by C++ code), to
160/// ensure that dropping the collection does not deallocate the underlying memory.
161#[derive(Clone, Default)]
162pub struct NoOpAllocator;
163
164impl Allocator for NoOpAllocator {
165    fn allocate(&self, _layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
166        Err(AllocError)
167    }
168
169    unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {
170        // No-op.
171    }
172
173    unsafe fn grow(
174        &self,
175        _ptr: NonNull<u8>,
176        _old_layout: Layout,
177        _new_layout: Layout,
178    ) -> Result<NonNull<[u8]>, AllocError> {
179        Err(AllocError)
180    }
181
182    unsafe fn shrink(
183        &self,
184        _ptr: NonNull<u8>,
185        _old_layout: Layout,
186        _new_layout: Layout,
187    ) -> Result<NonNull<[u8]>, AllocError> {
188        Err(AllocError)
189    }
190
191    fn allocate_zeroed(&self, _layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
192        Err(AllocError)
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_default_allocator() {
202        let layout = Layout::from_size_align(10, 1).unwrap();
203        let ptr = DefaultAllocator::default().allocate(layout).unwrap();
204        unsafe {
205            DefaultAllocator::default().deallocate(ptr.cast::<u8>(), layout);
206        }
207    }
208
209    #[test]
210    fn test_default_allocator_grow_shrink() {
211        let layout = Layout::from_size_align(10, 1).unwrap();
212        let ptr = DefaultAllocator::default().allocate(layout).unwrap();
213
214        let new_layout = Layout::from_size_align(20, 1).unwrap();
215        let grown_ptr = unsafe {
216            DefaultAllocator::default().grow(ptr.cast::<u8>(), layout, new_layout).unwrap()
217        };
218        assert!(grown_ptr.len() >= 20);
219
220        let shrunk_layout = Layout::from_size_align(5, 1).unwrap();
221        let shrunk_ptr = unsafe {
222            DefaultAllocator::default()
223                .shrink(grown_ptr.cast::<u8>(), new_layout, shrunk_layout)
224                .unwrap()
225        };
226        assert!(shrunk_ptr.len() >= 5);
227
228        unsafe {
229            DefaultAllocator::default().deallocate(shrunk_ptr.cast::<u8>(), shrunk_layout);
230        }
231    }
232
233    #[test]
234    fn test_default_allocator_zeroed() {
235        let layout = Layout::from_size_align(10, 1).unwrap();
236        let ptr = DefaultAllocator::default().allocate_zeroed(layout).unwrap();
237
238        // Verify it is zeroed!
239        let slice = unsafe { ptr.as_ref() };
240        for &b in slice {
241            assert_eq!(b, 0);
242        }
243
244        unsafe {
245            DefaultAllocator::default().deallocate(ptr.cast::<u8>(), layout);
246        }
247    }
248
249    #[test]
250    fn test_default_allocator_zero_sized() {
251        let allocator = DefaultAllocator::default();
252
253        let layout0 = Layout::from_size_align(0, 1).unwrap();
254        let ptr0 = allocator.allocate(layout0).unwrap();
255        assert_eq!(ptr0.len(), 0);
256
257        let ptr0_zeroed = allocator.allocate_zeroed(layout0).unwrap();
258        assert_eq!(ptr0_zeroed.len(), 0);
259
260        unsafe {
261            allocator.deallocate(ptr0.cast::<u8>(), layout0);
262            allocator.deallocate(ptr0_zeroed.cast::<u8>(), layout0);
263        }
264
265        let grown0 = unsafe { allocator.grow(ptr0.cast::<u8>(), layout0, layout0).unwrap() };
266        assert_eq!(grown0.len(), 0);
267
268        let layout10 = Layout::from_size_align(10, 1).unwrap();
269        let grown10 = unsafe { allocator.grow(ptr0.cast::<u8>(), layout0, layout10).unwrap() };
270        assert!(grown10.len() >= 10);
271
272        let shrunk0 = unsafe { allocator.shrink(grown10.cast::<u8>(), layout10, layout0).unwrap() };
273        assert_eq!(shrunk0.len(), 0);
274
275        let shrunk0_again =
276            unsafe { allocator.shrink(ptr0.cast::<u8>(), layout0, layout0).unwrap() };
277        assert_eq!(shrunk0_again.len(), 0);
278    }
279
280    #[test]
281    fn test_no_op_allocator() {
282        let allocator = NoOpAllocator;
283        let layout = Layout::from_size_align(10, 1).unwrap();
284
285        // allocate and allocate_zeroed should return Err
286        assert_eq!(allocator.allocate(layout), Err(AllocError));
287        assert_eq!(allocator.allocate_zeroed(layout), Err(AllocError));
288
289        // grow and shrink should return Err
290        let ptr = NonNull::dangling();
291        assert_eq!(unsafe { allocator.grow(ptr, layout, layout) }, Err(AllocError));
292        assert_eq!(unsafe { allocator.shrink(ptr, layout, layout) }, Err(AllocError));
293
294        // deallocate should not panic or do anything (no-op)
295        unsafe {
296            allocator.deallocate(ptr, layout);
297        }
298    }
299}