Skip to main content

range_alloc_arceos/
lib.rs

1//! A simple, fast range allocator for managing contiguous ranges of resources.
2//!
3//! This crate provides a [`RangeAllocator`] that efficiently allocates and frees
4//! contiguous ranges from an initial range. It uses a best-fit allocation strategy
5//! to minimize memory fragmentation.
6//!
7//! # Example
8//!
9//! ```
10//! use range_alloc_arceos::RangeAllocator;
11//!
12//! let mut allocator = RangeAllocator::new(0..100);
13//!
14//! // Allocate a range of length 10
15//! let range = allocator.allocate_range(10).unwrap();
16//! assert_eq!(range, 0..10);
17//!
18//! // Free the range when done
19//! allocator.free_range(range);
20//! ```
21
22#![no_std]
23
24extern crate alloc;
25
26use alloc::{vec, vec::Vec};
27use core::{
28    fmt::Debug,
29    iter::Sum,
30    ops::{Add, AddAssign, Range, Sub},
31};
32
33/// A range allocator that manages allocation and deallocation of contiguous ranges.
34///
35/// The allocator starts with an initial range and maintains a list of free ranges.
36/// It uses a best-fit allocation strategy to minimize fragmentation when allocating
37/// new ranges.
38///
39/// # Type Parameters
40///
41/// * `T` - The type used for range bounds. Must support arithmetic operations and ordering.
42#[derive(Debug)]
43pub struct RangeAllocator<T> {
44    /// The range this allocator covers.
45    initial_range: Range<T>,
46    /// A Vec of ranges in this heap which are unused.
47    /// Must be ordered with ascending range start to permit short circuiting allocation.
48    /// No two ranges in this vec may overlap.
49    free_ranges: Vec<Range<T>>,
50}
51
52/// Error type returned when a range allocation fails.
53///
54/// This error indicates that there is not enough contiguous space available
55/// to satisfy the allocation request, although there may be enough total free
56/// space if it were defragmented.
57#[derive(Clone, Debug, PartialEq)]
58pub struct RangeAllocationError<T> {
59    /// The total length of all free ranges combined.
60    ///
61    /// This value represents how much space would be available if all fragmented
62    /// free ranges could be combined into one contiguous range.
63    pub fragmented_free_length: T,
64}
65
66impl<T> RangeAllocator<T>
67where
68    T: Clone + Copy + Add<Output = T> + AddAssign + Sub<Output = T> + Eq + PartialOrd + Debug,
69{
70    /// Creates a new range allocator with the specified initial range.
71    ///
72    /// The entire initial range is marked as free and available for allocation.
73    ///
74    /// # Arguments
75    ///
76    /// * `range` - The initial range that this allocator will manage.
77    ///
78    /// # Example
79    ///
80    /// ```
81    /// use range_alloc_arceos::RangeAllocator;
82    ///
83    /// let allocator = RangeAllocator::new(0..1024);
84    /// ```
85    pub fn new(range: Range<T>) -> Self {
86        RangeAllocator {
87            initial_range: range.clone(),
88            free_ranges: vec![range],
89        }
90    }
91
92    /// Returns a reference to the initial range managed by this allocator.
93    ///
94    /// This is the range that was provided when the allocator was created,
95    /// or the expanded range if [`grow_to`](Self::grow_to) was called.
96    pub fn initial_range(&self) -> &Range<T> {
97        &self.initial_range
98    }
99
100    /// Grows the allocator's range to a new end point.
101    ///
102    /// This extends the upper bound of the initial range and makes the new space
103    /// available for allocation. If the last free range ends at the current upper
104    /// bound, it is extended; otherwise, a new free range is added.
105    ///
106    /// # Arguments
107    ///
108    /// * `new_end` - The new end point for the range (must be greater than the current end).
109    pub fn grow_to(&mut self, new_end: T) {
110        let initial_range_end = self.initial_range.end;
111        if let Some(last_range) = self
112            .free_ranges
113            .last_mut()
114            .filter(|last_range| last_range.end == initial_range_end)
115        {
116            last_range.end = new_end;
117        } else {
118            self.free_ranges.push(self.initial_range.end..new_end);
119        }
120
121        self.initial_range.end = new_end;
122    }
123
124    /// Allocates a contiguous range of the specified length.
125    ///
126    /// This method uses a best-fit allocation strategy to find the smallest free range
127    /// that can satisfy the request, minimizing fragmentation. If no single contiguous
128    /// range is large enough, it returns an error with information about the total
129    /// fragmented free space.
130    ///
131    /// # Arguments
132    ///
133    /// * `length` - The length of the range to allocate.
134    ///
135    /// # Returns
136    ///
137    /// * `Ok(Range<T>)` - The allocated range if successful.
138    /// * `Err(RangeAllocationError<T>)` - If allocation fails, containing information
139    ///   about the total fragmented free space available.
140    ///
141    /// # Example
142    ///
143    /// ```
144    /// use range_alloc_arceos::RangeAllocator;
145    ///
146    /// let mut allocator = RangeAllocator::new(0..100);
147    /// let range = allocator.allocate_range(20).unwrap();
148    /// assert_eq!(range, 0..20);
149    /// ```
150    pub fn allocate_range(&mut self, length: T) -> Result<Range<T>, RangeAllocationError<T>> {
151        assert_ne!(length + length, length);
152        let mut best_fit: Option<(usize, Range<T>)> = None;
153
154        // This is actually correct. With the trait bound as it is, we have
155        // no way to summon a value of 0 directly, so we make one by subtracting
156        // something from itself. Once the trait bound can be changed, this can
157        // be fixed.
158        #[allow(clippy::eq_op)]
159        let mut fragmented_free_length = length - length;
160        for (index, range) in self.free_ranges.iter().cloned().enumerate() {
161            let range_length = range.end - range.start;
162            fragmented_free_length += range_length;
163            if range_length < length {
164                continue;
165            } else if range_length == length {
166                // Found a perfect fit, so stop looking.
167                best_fit = Some((index, range));
168                break;
169            }
170            best_fit = Some(match best_fit {
171                Some((best_index, best_range)) => {
172                    // Find best fit for this allocation to reduce memory fragmentation.
173                    if range_length < best_range.end - best_range.start {
174                        (index, range)
175                    } else {
176                        (best_index, best_range.clone())
177                    }
178                }
179                None => (index, range),
180            });
181        }
182        match best_fit {
183            Some((index, range)) => {
184                if range.end - range.start == length {
185                    self.free_ranges.remove(index);
186                } else {
187                    self.free_ranges[index].start += length;
188                }
189                Ok(range.start..(range.start + length))
190            }
191            None => Err(RangeAllocationError {
192                fragmented_free_length,
193            }),
194        }
195    }
196
197    /// Frees a previously allocated range, making it available for future allocations.
198    ///
199    /// This method attempts to merge the freed range with adjacent free ranges to
200    /// reduce fragmentation. The freed range must be within the initial range and
201    /// must not be empty.
202    ///
203    /// # Arguments
204    ///
205    /// * `range` - The range to free. Must be within the allocator's initial range.
206    ///
207    /// # Panics
208    ///
209    /// Panics if the range is outside the initial range or if the range is empty
210    /// (start >= end).
211    ///
212    /// # Example
213    ///
214    /// ```
215    /// use range_alloc_arceos::RangeAllocator;
216    ///
217    /// let mut allocator = RangeAllocator::new(0..100);
218    /// let range = allocator.allocate_range(20).unwrap();
219    /// allocator.free_range(range);
220    /// ```
221    pub fn free_range(&mut self, range: Range<T>) {
222        assert!(self.initial_range.start <= range.start && range.end <= self.initial_range.end);
223        assert!(range.start < range.end);
224
225        // Get insertion position.
226        let i = self
227            .free_ranges
228            .iter()
229            .position(|r| r.start > range.start)
230            .unwrap_or(self.free_ranges.len());
231
232        // Try merging with neighboring ranges in the free list.
233        // Before: |left|-(range)-|right|
234        if i > 0 && range.start == self.free_ranges[i - 1].end {
235            // Merge with |left|.
236            self.free_ranges[i - 1].end =
237                if i < self.free_ranges.len() && range.end == self.free_ranges[i].start {
238                    // Check for possible merge with |left| and |right|.
239                    let right = self.free_ranges.remove(i);
240                    right.end
241                } else {
242                    range.end
243                };
244
245            return;
246        } else if i < self.free_ranges.len() && range.end == self.free_ranges[i].start {
247            // Merge with |right|.
248            self.free_ranges[i].start = if i > 0 && range.start == self.free_ranges[i - 1].end {
249                // Check for possible merge with |left| and |right|.
250                let left = self.free_ranges.remove(i - 1);
251                left.start
252            } else {
253                range.start
254            };
255
256            return;
257        }
258
259        // Debug checks
260        assert!(
261            (i == 0 || self.free_ranges[i - 1].end < range.start)
262                && (i >= self.free_ranges.len() || range.end < self.free_ranges[i].start)
263        );
264
265        self.free_ranges.insert(i, range);
266    }
267
268    /// Returns an iterator over allocated non-empty ranges
269    pub fn allocated_ranges(&self) -> impl Iterator<Item = Range<T>> + '_ {
270        let first = match self.free_ranges.first() {
271            Some(Range { ref start, .. }) if *start > self.initial_range.start => {
272                Some(self.initial_range.start..*start)
273            }
274            None => Some(self.initial_range.clone()),
275            _ => None,
276        };
277
278        let last = match self.free_ranges.last() {
279            Some(Range { end, .. }) if *end < self.initial_range.end => {
280                Some(*end..self.initial_range.end)
281            }
282            _ => None,
283        };
284
285        let mid = self
286            .free_ranges
287            .iter()
288            .zip(self.free_ranges.iter().skip(1))
289            .map(|(ra, rb)| ra.end..rb.start);
290
291        first.into_iter().chain(mid).chain(last)
292    }
293
294    /// Resets the allocator to its initial state.
295    ///
296    /// This marks the entire initial range as free, effectively deallocating
297    /// all previously allocated ranges.
298    pub fn reset(&mut self) {
299        self.free_ranges.clear();
300        self.free_ranges.push(self.initial_range.clone());
301    }
302
303    /// Returns `true` if no ranges have been allocated.
304    ///
305    /// This checks whether the allocator is in its initial state with all space free.
306    pub fn is_empty(&self) -> bool {
307        self.free_ranges.len() == 1 && self.free_ranges[0] == self.initial_range
308    }
309}
310
311impl<T: Copy + Sub<Output = T> + Sum> RangeAllocator<T> {
312    /// Returns the total amount of free space available across all free ranges.
313    ///
314    /// This sums the lengths of all free ranges, giving the total amount of space
315    /// that could be allocated if fragmentation is not an issue.
316    pub fn total_available(&self) -> T {
317        self.free_ranges
318            .iter()
319            .map(|range| range.end - range.start)
320            .sum()
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_basic_allocation() {
330        let mut alloc = RangeAllocator::new(0..10);
331        // Test if an allocation works
332        assert_eq!(alloc.allocate_range(4), Ok(0..4));
333        assert!(alloc.allocated_ranges().eq(core::iter::once(0..4)));
334        // Free the prior allocation
335        alloc.free_range(0..4);
336        // Make sure the free actually worked
337        assert_eq!(alloc.free_ranges, vec![0..10]);
338        assert!(alloc.allocated_ranges().eq(core::iter::empty()));
339    }
340
341    #[test]
342    fn test_out_of_space() {
343        let mut alloc = RangeAllocator::new(0..10);
344        // Test if the allocator runs out of space correctly
345        assert_eq!(alloc.allocate_range(10), Ok(0..10));
346        assert!(alloc.allocated_ranges().eq(core::iter::once(0..10)));
347        assert!(alloc.allocate_range(4).is_err());
348        alloc.free_range(0..10);
349    }
350
351    #[test]
352    fn test_grow() {
353        let mut alloc = RangeAllocator::new(0..11);
354        // Test if the allocator runs out of space correctly
355        assert_eq!(alloc.allocate_range(10), Ok(0..10));
356        assert!(alloc.allocated_ranges().eq(core::iter::once(0..10)));
357        assert!(alloc.allocate_range(4).is_err());
358        alloc.grow_to(20);
359        assert_eq!(alloc.allocate_range(4), Ok(10..14));
360        alloc.free_range(0..14);
361    }
362
363    #[test]
364    fn test_grow_with_hole_at_start() {
365        let mut alloc = RangeAllocator::new(0..6);
366
367        assert_eq!(alloc.allocate_range(3), Ok(0..3));
368        assert_eq!(alloc.allocate_range(3), Ok(3..6));
369        alloc.free_range(0..3);
370
371        alloc.grow_to(9);
372        assert_eq!(alloc.allocated_ranges().collect::<Vec<_>>(), [3..6]);
373    }
374    #[test]
375    fn test_grow_with_hole_in_middle() {
376        let mut alloc = RangeAllocator::new(0..6);
377
378        assert_eq!(alloc.allocate_range(2), Ok(0..2));
379        assert_eq!(alloc.allocate_range(2), Ok(2..4));
380        assert_eq!(alloc.allocate_range(2), Ok(4..6));
381        alloc.free_range(2..4);
382
383        alloc.grow_to(9);
384        assert_eq!(alloc.allocated_ranges().collect::<Vec<_>>(), [0..2, 4..6]);
385    }
386
387    #[test]
388    fn test_dont_use_block_that_is_too_small() {
389        let mut alloc = RangeAllocator::new(0..10);
390        // Allocate three blocks then free the middle one and check for correct state
391        assert_eq!(alloc.allocate_range(3), Ok(0..3));
392        assert_eq!(alloc.allocate_range(3), Ok(3..6));
393        assert_eq!(alloc.allocate_range(3), Ok(6..9));
394        alloc.free_range(3..6);
395        assert_eq!(alloc.free_ranges, vec![3..6, 9..10]);
396        assert_eq!(
397            alloc.allocated_ranges().collect::<Vec<Range<i32>>>(),
398            vec![0..3, 6..9]
399        );
400        // Now request space that the middle block can fill, but the end one can't.
401        assert_eq!(alloc.allocate_range(3), Ok(3..6));
402    }
403
404    #[test]
405    fn test_free_blocks_in_middle() {
406        let mut alloc = RangeAllocator::new(0..100);
407        // Allocate many blocks then free every other block.
408        assert_eq!(alloc.allocate_range(10), Ok(0..10));
409        assert_eq!(alloc.allocate_range(10), Ok(10..20));
410        assert_eq!(alloc.allocate_range(10), Ok(20..30));
411        assert_eq!(alloc.allocate_range(10), Ok(30..40));
412        assert_eq!(alloc.allocate_range(10), Ok(40..50));
413        assert_eq!(alloc.allocate_range(10), Ok(50..60));
414        assert_eq!(alloc.allocate_range(10), Ok(60..70));
415        assert_eq!(alloc.allocate_range(10), Ok(70..80));
416        assert_eq!(alloc.allocate_range(10), Ok(80..90));
417        assert_eq!(alloc.allocate_range(10), Ok(90..100));
418        assert_eq!(alloc.free_ranges, vec![]);
419        assert!(alloc.allocated_ranges().eq(core::iter::once(0..100)));
420        alloc.free_range(10..20);
421        alloc.free_range(30..40);
422        alloc.free_range(50..60);
423        alloc.free_range(70..80);
424        alloc.free_range(90..100);
425        // Check that the right blocks were freed.
426        assert_eq!(
427            alloc.free_ranges,
428            vec![10..20, 30..40, 50..60, 70..80, 90..100]
429        );
430        assert_eq!(
431            alloc.allocated_ranges().collect::<Vec<Range<i32>>>(),
432            vec![0..10, 20..30, 40..50, 60..70, 80..90]
433        );
434        // Fragment the memory on purpose a bit.
435        assert_eq!(alloc.allocate_range(6), Ok(10..16));
436        assert_eq!(alloc.allocate_range(6), Ok(30..36));
437        assert_eq!(alloc.allocate_range(6), Ok(50..56));
438        assert_eq!(alloc.allocate_range(6), Ok(70..76));
439        assert_eq!(alloc.allocate_range(6), Ok(90..96));
440        // Check for fragmentation.
441        assert_eq!(
442            alloc.free_ranges,
443            vec![16..20, 36..40, 56..60, 76..80, 96..100]
444        );
445        assert_eq!(
446            alloc.allocated_ranges().collect::<Vec<Range<i32>>>(),
447            vec![0..16, 20..36, 40..56, 60..76, 80..96]
448        );
449        // Fill up the fragmentation
450        assert_eq!(alloc.allocate_range(4), Ok(16..20));
451        assert_eq!(alloc.allocate_range(4), Ok(36..40));
452        assert_eq!(alloc.allocate_range(4), Ok(56..60));
453        assert_eq!(alloc.allocate_range(4), Ok(76..80));
454        assert_eq!(alloc.allocate_range(4), Ok(96..100));
455        // Check that nothing is free.
456        assert_eq!(alloc.free_ranges, vec![]);
457        assert!(alloc.allocated_ranges().eq(core::iter::once(0..100)));
458    }
459
460    #[test]
461    fn test_ignore_block_if_another_fits_better() {
462        let mut alloc = RangeAllocator::new(0..10);
463        // Allocate blocks such that the only free spaces available are 3..6 and 9..10
464        // in order to prepare for the next test.
465        assert_eq!(alloc.allocate_range(3), Ok(0..3));
466        assert_eq!(alloc.allocate_range(3), Ok(3..6));
467        assert_eq!(alloc.allocate_range(3), Ok(6..9));
468        alloc.free_range(3..6);
469        assert_eq!(alloc.free_ranges, vec![3..6, 9..10]);
470        assert_eq!(
471            alloc.allocated_ranges().collect::<Vec<Range<i32>>>(),
472            vec![0..3, 6..9]
473        );
474        // Now request space that can be filled by 3..6 but should be filled by 9..10
475        // because 9..10 is a perfect fit.
476        assert_eq!(alloc.allocate_range(1), Ok(9..10));
477    }
478
479    #[test]
480    fn test_merge_neighbors() {
481        let mut alloc = RangeAllocator::new(0..9);
482        assert_eq!(alloc.allocate_range(3), Ok(0..3));
483        assert_eq!(alloc.allocate_range(3), Ok(3..6));
484        assert_eq!(alloc.allocate_range(3), Ok(6..9));
485        alloc.free_range(0..3);
486        alloc.free_range(6..9);
487        alloc.free_range(3..6);
488        assert_eq!(alloc.free_ranges, vec![0..9]);
489        assert!(alloc.allocated_ranges().eq(core::iter::empty()));
490    }
491}