Skip to main content

osiris/mem/alloc/
bestfit.rs

1use core::{ops::Range, ptr::NonNull};
2
3use crate::hal::mem::PhysAddr;
4
5use crate::error::Result;
6
7/// The metadata that is before any block in the BestFitAllocator.
8struct BestFitMeta {
9    /// The size of the block in bytes.
10    size: usize,
11    /// The pointer to the next free block. This is `None` if the block is allocated.
12    next: Option<NonNull<u8>>,
13}
14
15/// This is an allocator implementation that uses the best fit strategy.
16/// That does mean, when we allocate a block, we try to find the smallest block that fits the requested size.
17/// Blocks are stored in a singly linked list. The important part is that the linked list is stored in-line with the memory blocks.
18/// This means that every block has a header that contains the size of the block and a pointer to the next block.
19#[proc_macros::fmt]
20pub struct BestFitAllocator {
21    /// Head of the free block list.
22    head: Option<NonNull<u8>>,
23    #[cfg(any(feature = "metrics", metrics))]
24    metrics: super::Metrics,
25}
26
27// Safety: BestFitAllocator is not Copy or Clone.
28// BestFitAllocator owns all its data exclusively.
29// The user must ensure that the returned pointers by malloc do not outlive the allocator.
30unsafe impl Send for BestFitAllocator {}
31// Safety: BestFitAllocator does only allow access to its data through &mut self.
32unsafe impl Sync for BestFitAllocator {}
33
34/// Implementation of the BestFitAllocator.
35impl BestFitAllocator {
36    pub const MIN_RANGE_SIZE: usize = size_of::<BestFitMeta>() + Self::align_up() + 1;
37
38    /// Creates a new BestFitAllocator.
39    ///
40    /// Returns the new BestFitAllocator.
41    pub const fn new() -> Self {
42        Self {
43            head: None,
44            #[cfg(any(feature = "metrics", metrics))]
45            metrics: super::Metrics::new(),
46        }
47    }
48
49    /// Adds a range of memory to the allocator.
50    ///
51    /// `range` - The range of memory to add.
52    ///
53    /// Returns `Ok(())` if the range was added successfully, otherwise an error.
54    ///
55    /// # Safety
56    ///
57    /// The range must be valid, 128bit aligned and must not overlapping with any other current or future range.
58    /// The range must also be at least as large as `MIN_RANGE_SIZE`.
59    /// Also the range must stay valid, for the whole lifetime of the allocator. Also the lifetime of any allocation is only valid as long as the allocator is valid.
60    pub unsafe fn add_range(&mut self, range: &Range<PhysAddr>) -> Result<()> {
61        let ptr = range.start;
62
63        // Check if the pointer is 128bit aligned.
64        if !ptr.is_multiple_of(align_of::<u128>()) {
65            return Err(kerr!(EINVAL));
66        }
67
68        if range.end.diff(range.start) < Self::MIN_RANGE_SIZE {
69            return Err(kerr!(EINVAL));
70        }
71
72        debug_assert!(range.end > range.start);
73        debug_assert!(range.end.diff(range.start) > size_of::<BestFitMeta>() + Self::align_up());
74        debug_assert!(range.end.as_usize() <= isize::MAX as usize);
75
76        // The user pointer is the pointer to the user memory. So we need to add the size of the meta data and possibly add padding.
77        let user_pointer = ptr + size_of::<BestFitMeta>() + Self::align_up();
78
79        let usable = range.end.diff(user_pointer);
80
81        // Set the current head as the next block, so we can add the new block to the head.
82        let meta = BestFitMeta {
83            size: usable,
84            next: self.head,
85        };
86
87        // Write the header to the memory.
88        unsafe { core::ptr::write(ptr.as_mut_ptr::<BestFitMeta>(), meta) };
89
90        // Set the head to the new block.
91        self.head = Some(unsafe { NonNull::new_unchecked(ptr.as_mut_ptr::<u8>()) });
92
93        #[cfg(any(feature = "metrics", metrics))]
94        self.metrics
95            .record_add_range(range.end.diff(range.start), usable);
96
97        Ok(())
98    }
99
100    /// Calculates the padding required to align the block. Note: We only align to 128bit.
101    ///
102    /// Returns the padding in bytes.
103    const fn align_up() -> usize {
104        let meta = size_of::<BestFitMeta>();
105        let align = align_of::<u128>();
106        // Calculate the padding required to align the block.
107        (align - (meta % align)) % align
108    }
109
110    /// Selects the best fit block for the given size.
111    ///
112    /// `size` - The size of the block.
113    ///
114    /// Returns the control pointer to the block and the control pointer to the previous block.
115    fn select_block(
116        &mut self,
117        size: usize,
118        requested: Option<PhysAddr>,
119    ) -> Result<(NonNull<u8>, Option<NonNull<u8>>)> {
120        let mut best_fit = Err(kerr!(ENOMEM));
121        let mut best_fit_size = usize::MAX;
122
123        let mut current = self.head;
124        let mut prev = None;
125
126        if let Some(requested) = requested {
127            while let Some(ptr) = current {
128                // Get the metadata of the block.
129                let meta = unsafe { ptr.cast::<BestFitMeta>().as_ref() };
130
131                if unsafe { Self::contains(meta, requested, size) } {
132                    return Ok((ptr, prev));
133                }
134
135                // Move to the next block.
136                prev = current;
137                current = meta.next;
138            }
139        }
140
141        // Iterate over all blocks and find the best fit.
142        while let Some(ptr) = current {
143            // Get the metadata of the block.
144            let meta = unsafe { ptr.cast::<BestFitMeta>().as_ref() };
145
146            // Check if the block is big enough and smaller than the current best fit.
147            if meta.size >= size && meta.size <= best_fit_size {
148                best_fit = Ok((ptr, prev));
149                best_fit_size = meta.size;
150            }
151
152            // Move to the next block.
153            prev = current;
154            current = meta.next;
155        }
156
157        best_fit
158    }
159
160    /// Calculates the user pointer from the control pointer.
161    ///
162    /// `ptr` - The control pointer.
163    ///
164    /// Returns the user pointer.
165    ///
166    /// # Safety
167    ///
168    /// The ptr must be a valid control pointer. Note: After the allocator which allocated the pointer is dropped, the control pointer is always considered invalid.
169    unsafe fn user_ptr(ptr: NonNull<u8>) -> NonNull<u8> {
170        debug_assert!(
171            (ptr.as_ptr() as usize)
172                <= isize::MAX as usize - size_of::<BestFitMeta>() - Self::align_up()
173        );
174        unsafe { ptr.byte_add(size_of::<BestFitMeta>() + Self::align_up()) }
175    }
176
177    /// Calculates the control pointer from the user pointer.
178    ///
179    /// `ptr` - The user pointer.
180    ///
181    /// Returns the control pointer.
182    ///
183    /// # Safety
184    ///
185    /// The ptr must be a valid user pointer. Note: After the allocator which allocated the pointer is dropped, the user pointer is always considered invalid.
186    unsafe fn control_ptr(ptr: NonNull<u8>) -> NonNull<u8> {
187        debug_assert!((ptr.as_ptr() as usize) > size_of::<BestFitMeta>() + Self::align_up());
188        unsafe { ptr.byte_sub(size_of::<BestFitMeta>() + Self::align_up()) }
189    }
190
191    unsafe fn contains(meta: &BestFitMeta, target: PhysAddr, size: usize) -> bool {
192        let begin = unsafe {
193            Self::user_ptr(NonNull::new_unchecked(
194                meta as *const BestFitMeta as *mut u8,
195            ))
196        };
197        debug_assert!(size > 0);
198
199        if target >= begin.into() {
200            if let Some(target) = target.checked_add(size) {
201                if target > (unsafe { begin.add(meta.size) }).into() {
202                    return false;
203                }
204            } else {
205                return false;
206            }
207            return true;
208        }
209        false
210    }
211}
212
213/// Implementation of the Allocator trait for BestFitAllocator.
214impl super::Allocator for BestFitAllocator {
215    /// Allocates a block of memory with the given size and alignment. Note: This function will always yield an invalid align for align > 128bit.
216    ///
217    /// `size` - The size of the block.
218    /// `align` - The alignment of the block.
219    ///
220    /// Returns the user pointer to the block if successful, otherwise an error.
221    ///
222    /// # Safety
223    ///
224    /// The caller must ensure that the returned pointer is not used after the allocator is dropped.
225    unsafe fn malloc<T>(
226        &mut self,
227        size: usize,
228        align: usize,
229        request: Option<PhysAddr>,
230    ) -> Result<NonNull<T>> {
231        // Check if the alignment is valid.
232        if align == 0 || align > align_of::<u128>() {
233            return Err(kerr!(EINVAL));
234        }
235
236        if let Some(request) = request {
237            if !request.is_multiple_of(align) {
238                return Err(kerr!(EINVAL));
239            }
240        }
241
242        // Check if the size is valid.
243        if size == 0 {
244            return Err(kerr!(EINVAL));
245        }
246
247        // For some cfg this warning is correct. But for others its not.
248        #[allow(clippy::absurd_extreme_comparisons)]
249        if size >= super::MAX_ADDR {
250            return Err(kerr!(EINVAL));
251        }
252
253        // Align the size.
254        let aligned_size = super::super::align_up(size);
255        debug_assert!(aligned_size >= size);
256        debug_assert!(aligned_size <= isize::MAX as usize);
257
258        // Tracking variables for O(1) metrics update after the allocation.
259        #[cfg(any(feature = "metrics", metrics))]
260        let mut free_sub: usize = 0;
261        #[cfg(any(feature = "metrics", metrics))]
262        let mut blocks_sub: usize = 0;
263
264        // Find the best fit block.
265        let (split, block, prev) = match self.select_block(aligned_size, request) {
266            Ok((block, prev)) => {
267                // Get the metadata of the block.
268                let meta = unsafe { block.cast::<BestFitMeta>().as_mut() };
269
270                // If we requested a specific address. The size must be extended by the offset from block start to the requested address.
271                let aligned_size = if let Some(request) = request {
272                    aligned_size + request.diff(unsafe { Self::user_ptr(block) }.into())
273                } else {
274                    aligned_size
275                };
276
277                // Calculate the amount of bytes until the beginning of the possibly next metadata.
278                let min = aligned_size.saturating_add(size_of::<BestFitMeta>() + Self::align_up());
279
280                debug_assert!(
281                    (block.as_ptr() as usize)
282                        <= isize::MAX as usize
283                            - meta.size
284                            - size_of::<BestFitMeta>()
285                            - Self::align_up()
286                );
287
288                debug_assert!(
289                    meta.size < isize::MAX as usize - size_of::<BestFitMeta>() - Self::align_up()
290                );
291
292                // If the block is big enough to split. Then it also needs to be big enough to store the metadata + align of the next block.
293                if meta.size > min {
294                    // Split: old free block (meta.size) leaves, remainder (meta.size - min) stays.
295                    // Net free_bytes change: -min. free_blocks unchanged (one out, one in).
296                    #[cfg(any(feature = "metrics", metrics))]
297                    {
298                        free_sub = min;
299                    }
300
301                    // Calculate the remaining size of the block and thus the next metadata.
302                    let remaining_meta = BestFitMeta {
303                        size: meta.size - min,
304                        next: meta.next,
305                    };
306
307                    // Shrink the current block to the requested aligned_size + padding (which is not available to the user).
308                    meta.size = aligned_size;
309
310                    // Calculate the pointer to the next metadata.
311                    let ptr = unsafe { Self::user_ptr(block).byte_add(aligned_size) };
312
313                    unsafe {
314                        // Write the new metadata to the memory.
315                        ptr.cast::<BestFitMeta>().write(remaining_meta);
316                    }
317
318                    // If there is a previous block, we insert the new block after it. Otherwise we set it as the new head.
319                    if let Some(prev) = prev {
320                        let prev_meta = unsafe { prev.cast::<BestFitMeta>().as_mut() };
321                        prev_meta.next = Some(ptr);
322                    } else {
323                        self.head = Some(ptr);
324                    }
325
326                    // The next block of an allocated block is always None.
327                    meta.next = None;
328
329                    (true, block, prev)
330                } else {
331                    // No split: entire free block (meta.size) is consumed.
332                    #[cfg(any(feature = "metrics", metrics))]
333                    {
334                        free_sub = meta.size;
335                        blocks_sub = 1;
336                    }
337
338                    (false, block, prev)
339                }
340            }
341            Err(_) => {
342                let (block, prev) = self.select_block(size, request)?;
343                // Retry succeeded with original size; always no-split.
344                #[cfg(any(feature = "metrics", metrics))]
345                {
346                    let meta = unsafe { block.cast::<BestFitMeta>().as_ref() };
347                    free_sub = meta.size;
348                    blocks_sub = 1;
349                }
350                (false, block, prev)
351            }
352        };
353
354        if !split {
355            // Get the metadata of the block.
356            let meta = unsafe { block.cast::<BestFitMeta>().as_mut() };
357
358            if let Some(prev) = prev {
359                let prev_meta = unsafe { prev.cast::<BestFitMeta>().as_mut() };
360                // If there is a previous block, we remove the current block from the list. Ie. we set the next block of the previous block to the next block of the current block.
361                prev_meta.next = meta.next;
362            } else {
363                // If there is no previous block, we set the next block as the new head.
364                self.head = meta.next;
365            }
366
367            // The next block of an allocated block is always None.
368            meta.next = None;
369        }
370
371        if let Some(request) = request {
372            debug_assert!(unsafe {
373                Self::contains(block.cast::<BestFitMeta>().as_ref(), request, size)
374            });
375        }
376
377        #[cfg(any(feature = "metrics", metrics))]
378        self.metrics.record_alloc(free_sub, blocks_sub);
379
380        // Return the user pointer.
381        Ok(unsafe { Self::user_ptr(block).cast() })
382    }
383
384    /// Frees a block of memory.
385    ///
386    /// `ptr` - The pointer to the block.
387    /// `size` - The size of the block. (This is used to check if the size of the block is correct.)
388    unsafe fn free<T>(&mut self, ptr: NonNull<T>, size: usize) {
389        let block = unsafe { Self::control_ptr(ptr.cast()) };
390
391        // Walking the free list catches a double-free before it can self-loop the list
392        // and turn the next `malloc` into an infinite traversal.
393        let mut walk = self.head;
394        while let Some(p) = walk {
395            bug_on!(p == block, "double free");
396            walk = unsafe { p.cast::<BestFitMeta>().as_ref().next };
397        }
398
399        let meta = unsafe { block.cast::<BestFitMeta>().as_mut() };
400
401        // The next block of a free block is always the current head. We essentially insert the block at the beginning of the list.
402        meta.next = self.head;
403
404        // Check if the size of the block is correct.
405        bug_on!(
406            size > meta.size,
407            "allocation size {} is larger than block size {}",
408            size,
409            meta.size
410        );
411
412        // Set the block as the new head.
413        self.head = Some(block);
414
415        #[cfg(any(feature = "metrics", metrics))]
416        self.metrics.record_free(meta.size);
417    }
418}
419
420#[cfg(any(feature = "metrics", metrics))]
421impl BestFitAllocator {
422    pub fn metrics(&self) -> super::Metrics {
423        self.metrics
424    }
425}
426
427// TESTING ------------------------------------------------------------------------------------------------------------
428
429#[cfg(all(test, any(feature = "metrics", metrics)))]
430mod metrics_tests {
431    use super::super::*;
432    use super::*;
433    use core::mem::size_of;
434
435    fn alloc_range(length: usize) -> std::ops::Range<crate::hal::mem::PhysAddr> {
436        use crate::hal::mem::PhysAddr;
437        let layout = std::alloc::Layout::from_size_align(length, align_of::<u128>()).unwrap();
438        let ptr = unsafe { std::alloc::alloc(layout) };
439        if ptr.is_null() {
440            std::alloc::handle_alloc_error(layout);
441        }
442        PhysAddr::new(ptr as usize)..PhysAddr::new(ptr as usize + length)
443    }
444
445    #[test]
446    fn metrics_fresh_allocator_is_zero() {
447        let allocator = BestFitAllocator::new();
448        let m = allocator.metrics();
449        assert_eq!(m.total_bytes, 0);
450        assert_eq!(m.free_bytes, 0);
451        assert_eq!(m.allocated_bytes(), 0);
452        assert_eq!(m.free_blocks, 0);
453        assert_eq!(m.alloc_count, 0);
454        assert_eq!(m.free_count, 0);
455    }
456
457    #[test]
458    fn metrics_after_add_range() {
459        let mut allocator = BestFitAllocator::new();
460        let range_len = 4096usize;
461        let range = alloc_range(range_len);
462        unsafe { allocator.add_range(&range).unwrap() };
463
464        let m = allocator.metrics();
465        assert_eq!(m.total_bytes, range_len);
466        assert_eq!(m.free_blocks, 1);
467        assert!(m.free_bytes > 0);
468        assert!(m.free_bytes < range_len, "metadata must consume some bytes");
469        assert_eq!(m.allocated_bytes(), range_len - m.free_bytes);
470        assert_eq!(m.alloc_count, 0);
471        assert_eq!(m.free_count, 0);
472    }
473
474    #[test]
475    fn metrics_alloc_increments_count_and_reduces_free() {
476        let mut allocator = BestFitAllocator::new();
477        let range = alloc_range(4096);
478        unsafe { allocator.add_range(&range).unwrap() };
479        let before = allocator.metrics();
480
481        let _ptr = unsafe { allocator.malloc::<u8>(128, 1, None).unwrap() };
482        let after = allocator.metrics();
483
484        assert_eq!(after.alloc_count, 1);
485        assert_eq!(after.free_count, 0);
486        assert!(after.free_bytes < before.free_bytes);
487    }
488
489    #[test]
490    fn metrics_free_increments_count_and_restores_free_bytes() {
491        let mut allocator = BestFitAllocator::new();
492        let range = alloc_range(4096);
493        unsafe { allocator.add_range(&range).unwrap() };
494
495        let ptr = unsafe { allocator.malloc::<u8>(128, 1, None).unwrap() };
496        let after_alloc = allocator.metrics();
497
498        unsafe { allocator.free(ptr, 128) };
499        let after_free = allocator.metrics();
500
501        assert_eq!(after_free.alloc_count, 1);
502        assert_eq!(after_free.free_count, 1);
503        // Freeing must return bytes to the free pool.
504        assert!(after_free.free_bytes > after_alloc.free_bytes);
505    }
506
507    #[test]
508    fn metrics_free_blocks_count() {
509        let mut allocator = BestFitAllocator::new();
510        let range = alloc_range(4096);
511        unsafe { allocator.add_range(&range).unwrap() };
512
513        let p1 = unsafe { allocator.malloc::<u8>(128, 1, None).unwrap() };
514        let p2 = unsafe { allocator.malloc::<u8>(128, 1, None).unwrap() };
515        let after_two_allocs = allocator.metrics();
516
517        unsafe { allocator.free(p1, 128) };
518        let after_free1 = allocator.metrics();
519
520        unsafe { allocator.free(p2, 128) };
521        let after_free2 = allocator.metrics();
522
523        // Each free prepends one block to the free list.
524        assert_eq!(after_free1.free_blocks, after_two_allocs.free_blocks + 1);
525        assert_eq!(after_free2.free_blocks, after_two_allocs.free_blocks + 2);
526        assert_eq!(after_free2.alloc_count, 2);
527        assert_eq!(after_free2.free_count, 2);
528    }
529
530    #[test]
531    fn metrics_largest_free_block_single_range() {
532        let mut allocator = BestFitAllocator::new();
533        let range = alloc_range(4096);
534        unsafe { allocator.add_range(&range).unwrap() };
535
536        let m = allocator.metrics();
537        // Single block: all free bytes in one block.
538        assert_eq!(m.free_blocks, 1);
539
540        let _p = unsafe { allocator.malloc::<u8>(128, 1, None).unwrap() };
541        let m2 = allocator.metrics();
542        // Free bytes shrink after allocation.
543        assert!(m2.free_bytes <= m.free_bytes);
544    }
545
546    #[test]
547    fn metrics_multiple_ranges_total_bytes() {
548        let mut allocator = BestFitAllocator::new();
549        const RANGE_LEN: usize = 1024;
550        const RANGES: usize = 3;
551
552        for _ in 0..RANGES {
553            let range = alloc_range(RANGE_LEN);
554            unsafe { allocator.add_range(&range).unwrap() };
555        }
556
557        let m = allocator.metrics();
558        assert_eq!(m.total_bytes, RANGE_LEN * RANGES);
559        assert_eq!(m.free_blocks, RANGES);
560    }
561
562    #[test]
563    fn metrics_exact_fit_no_split() {
564        // Allocate the entire usable space of a single-block range so no split occurs.
565        let mut allocator = BestFitAllocator::new();
566        let overhead = size_of::<BestFitMeta>() + BestFitAllocator::align_up();
567        let user_size = 128usize;
568        let range = alloc_range(user_size + overhead);
569        unsafe { allocator.add_range(&range).unwrap() };
570
571        let before = allocator.metrics();
572        assert_eq!(before.free_blocks, 1);
573
574        let ptr = unsafe { allocator.malloc::<u8>(user_size, 1, None).unwrap() };
575        let after_alloc = allocator.metrics();
576        // Exact fit: no remainder block left.
577        assert_eq!(after_alloc.free_blocks, 0);
578        assert_eq!(after_alloc.free_bytes, 0);
579
580        unsafe { allocator.free(ptr, user_size) };
581        let after_free = allocator.metrics();
582        assert_eq!(after_free.free_blocks, 1);
583        assert_eq!(after_free.free_bytes, before.free_bytes);
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use crate::mem::align_up;
590    use hal_api::PosixError;
591
592    use super::super::*;
593    use super::*;
594
595    fn verify_block(user_ptr: NonNull<u8>, size: usize, next: Option<NonNull<u8>>) {
596        let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) };
597        let meta = unsafe { control_ptr.cast::<BestFitMeta>().as_ref() };
598
599        assert!(meta.size >= size);
600        assert_eq!(meta.next, next);
601    }
602
603    fn verify_ptrs_not_overlaping(ptrs: &[(NonNull<u8>, usize)]) {
604        for (i, (ptr1, size1)) in ptrs.iter().enumerate() {
605            for (j, (ptr2, size2)) in ptrs.iter().enumerate() {
606                if i == j {
607                    continue;
608                }
609
610                let begin1 = ptr1.as_ptr() as usize;
611                let end1 = begin1 + size1;
612                let begin2 = ptr2.as_ptr() as usize;
613                let end2 = begin2 + size2;
614
615                assert!(end1 <= begin2 || end2 <= begin1);
616                assert!(begin1 != begin2);
617                assert!(end1 != end2);
618                assert!(*size1 > 0);
619                assert!(*size2 > 0);
620                assert!(end1 > begin1);
621                assert!(end2 > begin2);
622            }
623        }
624    }
625
626    fn alloc_range(length: usize) -> Range<PhysAddr> {
627        let alloc_range = std::alloc::Layout::from_size_align(length, align_of::<u128>()).unwrap();
628        let ptr = unsafe { std::alloc::alloc(alloc_range) };
629        PhysAddr::new(ptr as usize)..PhysAddr::new(ptr as usize + length)
630    }
631
632    #[test]
633    fn allocate_one() {
634        let mut allocator = BestFitAllocator::new();
635
636        let range = alloc_range(4096);
637        unsafe {
638            allocator.add_range(&range).unwrap();
639        }
640
641        let ptr = unsafe { allocator.malloc(128, 1, None).unwrap() };
642
643        verify_block(ptr, 128, None);
644    }
645
646    #[test]
647    fn alloc_request() {
648        let mut allocator = BestFitAllocator::new();
649
650        let range = alloc_range(4096);
651        unsafe {
652            allocator.add_range(&range).unwrap();
653        }
654
655        let request = range.start + 128;
656        let ptr = unsafe { allocator.malloc::<u8>(128, 1, Some(request)).unwrap() };
657
658        // Check that the returned pointer contains the requested address.
659        let meta = unsafe {
660            BestFitAllocator::control_ptr(ptr)
661                .cast::<BestFitMeta>()
662                .as_ref()
663        };
664        assert!(unsafe { BestFitAllocator::contains(meta, request, 128) });
665    }
666
667    #[test]
668    fn alloc_request_to_big() {
669        let mut allocator = BestFitAllocator::new();
670
671        let range = alloc_range(4096);
672        unsafe {
673            allocator.add_range(&range).unwrap();
674        }
675
676        let request = range.start + 4096;
677        let ptr = unsafe { allocator.malloc::<u8>(128, 1, Some(request)) };
678
679        assert!(ptr.is_err_and(|e| e.kind == PosixError::ENOMEM));
680    }
681
682    #[test]
683    fn alloc_request_not_aligned() {
684        let mut allocator = BestFitAllocator::new();
685
686        let range = alloc_range(4096);
687        unsafe {
688            allocator.add_range(&range).unwrap();
689        }
690
691        let request = range.start + 127;
692        let ptr = unsafe { allocator.malloc::<u8>(128, 8, Some(request)) };
693
694        assert!(ptr.is_err_and(|e| e.kind == PosixError::EINVAL));
695    }
696
697    #[test]
698    fn alloc_request_not_available() {
699        let mut allocator = BestFitAllocator::new();
700
701        let range = alloc_range(4096);
702        unsafe {
703            allocator.add_range(&range).unwrap();
704        }
705
706        let request = range.start + 128;
707        let ptr = unsafe { allocator.malloc::<u8>(128, 1, Some(request)).unwrap() };
708        verify_block(ptr, 128, None);
709
710        let ptr = unsafe { allocator.malloc::<u8>(128, 1, Some(request)) };
711        assert!(ptr.is_err_and(|e| e.kind == PosixError::ENOMEM));
712    }
713
714    #[test]
715    fn alloc_request_out_of_range() {
716        let mut allocator = BestFitAllocator::new();
717
718        let range = alloc_range(4096);
719        unsafe {
720            allocator.add_range(&range).unwrap();
721        }
722
723        let request = range.end + 128;
724        let ptr = unsafe { allocator.malloc::<u8>(128, 1, Some(request)) };
725
726        assert!(ptr.is_err_and(|e| e.kind == PosixError::ENOMEM));
727    }
728
729    #[test]
730    fn alloc_alot() {
731        let mut allocator = BestFitAllocator::new();
732        const CNT: usize = 100;
733        const SIZE: usize = 128;
734
735        let range = alloc_range(SIZE * CNT * 2);
736        unsafe {
737            allocator.add_range(&range).unwrap();
738        }
739
740        let mut ptrs = Vec::new();
741        for _ in 0..CNT {
742            let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
743            verify_block(ptr, SIZE, None);
744            ptrs.push((ptr, SIZE));
745        }
746
747        verify_ptrs_not_overlaping(ptrs.as_slice());
748    }
749
750    #[test]
751    fn alloc_exact() {
752        let mut allocator = BestFitAllocator::new();
753        const CNT: usize = 10;
754        const SIZE: usize = 128;
755
756        let range =
757            alloc_range((SIZE + size_of::<BestFitMeta>() + BestFitAllocator::align_up()) * CNT);
758        unsafe {
759            allocator.add_range(&range).unwrap();
760        }
761
762        let mut ptrs = Vec::new();
763        for _ in 0..CNT {
764            let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
765            verify_block(ptr, SIZE, None);
766            ptrs.push((ptr, SIZE));
767        }
768
769        verify_ptrs_not_overlaping(ptrs.as_slice());
770    }
771
772    #[test]
773    fn alloc_oom() {
774        let mut allocator = BestFitAllocator::new();
775        const CNT: usize = 10;
776        const SIZE: usize = 128;
777
778        let range =
779            alloc_range((SIZE + size_of::<BestFitMeta>() + BestFitAllocator::align_up()) * CNT - 1);
780        unsafe {
781            allocator.add_range(&range).unwrap();
782        }
783
784        let mut ptrs = Vec::new();
785        for _ in 0..CNT - 1 {
786            let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
787            verify_block(ptr, SIZE, None);
788            ptrs.push(ptr);
789        }
790
791        let ptr = unsafe { allocator.malloc::<u8>(SIZE, 1, None) };
792        assert!(ptr.is_err_and(|e| e.kind == PosixError::ENOMEM));
793    }
794
795    #[test]
796    fn alloc_no_oom_through_free() {
797        let mut allocator = BestFitAllocator::new();
798        const SIZE: usize = 128;
799
800        let range = alloc_range(SIZE + size_of::<BestFitMeta>() + BestFitAllocator::align_up());
801        unsafe {
802            allocator.add_range(&range).unwrap();
803        }
804
805        let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
806        verify_block(ptr, SIZE, None);
807
808        unsafe {
809            allocator.free(ptr, SIZE);
810        }
811
812        let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
813        verify_block(ptr, SIZE, None);
814    }
815
816    #[test]
817    fn multi_range_alloc() {
818        let mut allocator = BestFitAllocator::new();
819        const CNT: usize = 10;
820        const SIZE: usize = 128;
821
822        let mut ranges = Vec::new();
823        for _ in 0..CNT {
824            let range = alloc_range(SIZE + size_of::<BestFitMeta>() + BestFitAllocator::align_up());
825            unsafe {
826                allocator.add_range(&range).unwrap();
827            }
828            ranges.push(range);
829        }
830
831        let mut ptrs = Vec::new();
832        for _ in 0..CNT {
833            let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
834            verify_block(ptr, SIZE, None);
835            ptrs.push((ptr, SIZE));
836        }
837
838        verify_ptrs_not_overlaping(ptrs.as_slice());
839    }
840
841    #[test]
842    fn multi_range_no_oom_through_free() {
843        // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom.
844        let mut allocator = BestFitAllocator::new();
845
846        const CNT: usize = 10;
847        const SIZE: usize = 128;
848
849        let mut ranges = Vec::new();
850        for _ in 0..CNT {
851            let range = alloc_range(SIZE + size_of::<BestFitMeta>() + BestFitAllocator::align_up());
852            unsafe {
853                allocator.add_range(&range).unwrap();
854            }
855            ranges.push(range);
856        }
857
858        let mut ptrs = Vec::new();
859
860        let ptr = unsafe { allocator.malloc::<u8>(SIZE, 1, None).unwrap() };
861
862        for _ in 0..CNT - 1 {
863            let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
864            verify_block(ptr, SIZE, None);
865            ptrs.push((ptr, SIZE));
866        }
867
868        unsafe {
869            allocator.free(ptr, SIZE);
870        }
871
872        let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
873        ptrs.push((ptr, SIZE));
874
875        verify_ptrs_not_overlaping(ptrs.as_slice());
876    }
877
878    #[test]
879    fn free_corrupts_metadata() {
880        let mut allocator = BestFitAllocator::new();
881        const SIZE: usize = 17;
882        const ALIGNED: usize = 32;
883        assert!(align_up(SIZE) == ALIGNED);
884
885        let range = alloc_range(ALIGNED + size_of::<BestFitMeta>() + BestFitAllocator::align_up());
886        unsafe {
887            allocator.add_range(&range).unwrap();
888        }
889
890        let ptr1: core::ptr::NonNull<u8> = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
891
892        unsafe {
893            allocator.free(ptr1, SIZE);
894        }
895
896        let ptr2: core::ptr::NonNull<u8> = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
897
898        unsafe {
899            allocator.free(ptr2, SIZE);
900        }
901    }
902
903    #[test]
904    #[should_panic(expected = "double free")]
905    fn double_free_panics() {
906        let mut allocator = BestFitAllocator::new();
907        let range = alloc_range(4096);
908        unsafe {
909            allocator.add_range(&range).unwrap();
910        }
911
912        let ptr = unsafe { allocator.malloc::<u8>(128, 1, None).unwrap() };
913        unsafe {
914            allocator.free(ptr, 128);
915            // Without the defensive walk in free(), this re-insert builds a
916            // self-loop in the free list and the next malloc spins forever.
917            allocator.free(ptr, 128);
918        }
919    }
920
921    #[test]
922    fn multi_range_oom() {
923        // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom.
924        let mut allocator = BestFitAllocator::new();
925
926        const CNT: usize = 10;
927        const SIZE: usize = 128;
928
929        let mut ranges = Vec::new();
930        for _ in 0..CNT {
931            let range = alloc_range(SIZE + size_of::<BestFitMeta>() + BestFitAllocator::align_up());
932            unsafe {
933                allocator.add_range(&range).unwrap();
934            }
935            ranges.push(range);
936        }
937
938        let mut ptrs = Vec::new();
939
940        for _ in 0..CNT {
941            let ptr = unsafe { allocator.malloc(SIZE, 1, None).unwrap() };
942            verify_block(ptr, SIZE, None);
943            ptrs.push((ptr, SIZE));
944        }
945
946        let ptr = unsafe { allocator.malloc::<u8>(SIZE, 1, None) };
947        assert!(ptr.is_err_and(|e| e.kind == PosixError::ENOMEM));
948
949        verify_ptrs_not_overlaping(ptrs.as_slice());
950    }
951}
952
953// END TESTING --------------------------------------------------------------------------------------------------------
954
955// VERIFICATION -------------------------------------------------------------------------------------------------------
956#[cfg(kani)]
957mod verification {
958    use super::*;
959    use crate::mem::alloc::Allocator;
960    use crate::mem::alloc::MAX_ADDR;
961
962    fn verify_block(user_ptr: NonNull<u8>, size: usize, next: Option<NonNull<u8>>) {
963        let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) };
964        let meta = unsafe { control_ptr.cast::<BestFitMeta>().as_ref() };
965
966        assert!(meta.size >= size);
967        assert_eq!(meta.next, next);
968    }
969
970    fn alloc_range(length: usize) -> Option<Range<PhysAddr>> {
971        let alloc_range = std::alloc::Layout::from_size_align(length, align_of::<u128>()).unwrap();
972        let ptr = unsafe { std::alloc::alloc(alloc_range) };
973
974        if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) {
975            None
976        } else {
977            Some(PhysAddr::new(ptr as usize)..PhysAddr::new(ptr as usize + length))
978        }
979    }
980
981    #[kani::proof]
982    #[kani::unwind(2)]
983    fn allocate_one() {
984        let mut allocator = BestFitAllocator::new();
985
986        let size: usize = kani::any();
987        kani::assume(size < MAX_ADDR - size_of::<BestFitMeta>() - BestFitAllocator::align_up());
988        kani::assume(size > 0);
989        let larger_size: usize = kani::any_where(|&x| {
990            x > size + size_of::<BestFitMeta>() + BestFitAllocator::align_up() && x < MAX_ADDR
991        });
992
993        if let Some(range) = alloc_range(larger_size) {
994            unsafe {
995                assert_eq!(allocator.add_range(&range), Ok(()));
996            }
997
998            let ptr = unsafe { allocator.malloc(size, 1, None).unwrap() };
999
1000            verify_block(ptr, size, None);
1001        }
1002    }
1003}
1004// END VERIFICATION ---------------------------------------------------------------------------------------------------