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