From 9e28700aedecb67d4b242e7dfc4ee2a731a19841 Mon Sep 17 00:00:00 2001 From: Graham MacDonald Date: Wed, 22 May 2024 21:17:11 +0100 Subject: [PATCH] Working on vmem allocation Signed-off-by: Graham MacDonald --- aarch64/src/main.rs | 9 +- aarch64/src/vm.rs | 6 +- aarch64/src/vmalloc.rs | 32 ++- port/src/boundarytag.rs | 493 +++++++++++++++++++++++++++++++++------- 4 files changed, 441 insertions(+), 99 deletions(-) diff --git a/aarch64/src/main.rs b/aarch64/src/main.rs index 16d3ab0..dad0338 100644 --- a/aarch64/src/main.rs +++ b/aarch64/src/main.rs @@ -61,7 +61,7 @@ fn print_memory_info() { let vc_mem = mailbox::get_vc_memory(); println!(" Video:\t{vc_mem} ({:#x})", vc_mem.size()); - println!("Memory usage::"); + println!("Memory usage:"); let (used, total) = pagealloc::usage_bytes(); println!(" Used:\t\t{used:#016x}"); println!(" Total:\t{total:#016x}"); @@ -128,6 +128,13 @@ pub extern "C" fn main9(dtb_va: usize) { println!("looping now"); + { + let test = vmalloc::alloc(1024); + println!("test alloc: {:p}", test); + let test2 = vmalloc::alloc(1024); + println!("test alloc: {:p}", test2); + } + #[allow(clippy::empty_loop)] loop {} } diff --git a/aarch64/src/vm.rs b/aarch64/src/vm.rs index eacccb9..27b3a06 100644 --- a/aarch64/src/vm.rs +++ b/aarch64/src/vm.rs @@ -2,8 +2,8 @@ use crate::{ kmem::{ - from_ptr_to_physaddr, kernel_bss_physrange, kernel_data_physrange, kernel_heap_physrange, - kernel_text_physrange, physaddr_as_ptr_mut, physaddr_as_virt, + from_ptr_to_physaddr, heap_virtrange, kernel_bss_physrange, kernel_data_physrange, + kernel_heap_physrange, kernel_text_physrange, physaddr_as_ptr_mut, physaddr_as_virt, }, pagealloc, registers::rpi_mmio, @@ -469,7 +469,7 @@ fn print_pte(indent: usize, i: usize, level: Level, pte: Entry) { pub unsafe fn init(kpage_table: &mut PageTable, dtb_range: PhysRange, available_mem: PhysRange) { pagealloc::init_page_allocator(); - vmalloc::init(); + vmalloc::init(heap_virtrange()); // We use recursive page tables, but we have to be careful in the init call, // since the kpage_table is not currently pointed to by ttbr1_el1. Any diff --git a/aarch64/src/vmalloc.rs b/aarch64/src/vmalloc.rs index 0e0ecb7..df677d5 100644 --- a/aarch64/src/vmalloc.rs +++ b/aarch64/src/vmalloc.rs @@ -3,40 +3,54 @@ use core::mem::MaybeUninit; use port::{ boundarytag::Arena, mcslock::{Lock, LockNode}, + mem::VirtRange, }; -use crate::kmem::heap_virtrange; - static VMALLOC: Lock> = Lock::new("vmalloc", None); +static mut EARLY_TAGS_PAGE: [u8; 4096] = [0; 4096]; + struct VmAlloc { - _heap_arena: Arena, + heap_arena: Arena, } impl VmAlloc { - fn new() -> Self { - let heap_range = heap_virtrange(); + fn new(heap_range: VirtRange) -> Self { let quantum = 4096; + + let early_tags_ptr = unsafe { &EARLY_TAGS_PAGE as *const _ as usize }; + let early_tags_size = unsafe { EARLY_TAGS_PAGE.len() }; + let early_tags_range = VirtRange::with_len(early_tags_ptr, early_tags_size); + Self { - _heap_arena: Arena::new_with_static_range( + heap_arena: Arena::new_with_static_range( "heap", heap_range.start(), heap_range.size(), quantum, - heap_range, + early_tags_range, ), } } } -pub fn init() { +pub fn init(heap_range: VirtRange) { let node = LockNode::new(); let mut vmalloc = VMALLOC.lock(&node); *vmalloc = Some({ static mut MAYBE_VMALLOC: MaybeUninit = MaybeUninit::uninit(); unsafe { - MAYBE_VMALLOC.write(VmAlloc::new()); + MAYBE_VMALLOC.write(VmAlloc::new(heap_range)); MAYBE_VMALLOC.assume_init_mut() } }); } + +// TODO Add VmFlag (BestFit, InstantFit, NextFit) + +pub fn alloc(size: usize) -> *mut u8 { + let node = LockNode::new(); + let mut lock = VMALLOC.lock(&node); + let vmalloc = lock.as_deref_mut().unwrap(); + vmalloc.heap_arena.alloc(size) +} diff --git a/port/src/boundarytag.rs b/port/src/boundarytag.rs index 549660e..62f846d 100644 --- a/port/src/boundarytag.rs +++ b/port/src/boundarytag.rs @@ -1,12 +1,23 @@ -use core::{ptr::null_mut, slice}; +use core::{fmt, ptr::null_mut, slice}; use crate::mem::VirtRange; +#[cfg(not(test))] +use crate::println; + +// TODO reserve recursive area in vmem + #[derive(Debug, PartialEq)] pub enum BoundaryError { ZeroSize, } +#[derive(Debug, PartialEq)] +pub enum AllocError { + NoSpace, +} + +#[cfg(test)] type BoundaryResult = core::result::Result; #[derive(Copy, Clone, Debug, PartialEq)] @@ -16,6 +27,7 @@ struct Boundary { } impl Boundary { + #[cfg(test)] fn new(start: usize, size: usize) -> BoundaryResult { if size == 0 { Err(BoundaryError::ZeroSize) @@ -24,6 +36,10 @@ impl Boundary { } } + fn new_unchecked(start: usize, size: usize) -> Self { + Self { start, size } + } + #[allow(dead_code)] fn overlaps(&self, other: &Boundary) -> bool { let boundary_end = self.start + self.size; @@ -32,20 +48,81 @@ impl Boundary { || (self.start < tag_end && boundary_end >= tag_end) || (self.start <= other.start && boundary_end >= tag_end) } + + fn end(&self) -> usize { + self.start + self.size + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +enum TagType { + Allocated, + Free, + Span, } +#[derive(Copy, Clone, Debug, PartialEq)] struct Tag { + tag_type: TagType, boundary: Boundary, - next: *mut Tag, - prev: *mut Tag, } impl Tag { + fn new(tag_type: TagType, boundary: Boundary) -> Self { + Self { tag_type, boundary } + } + + #[cfg(test)] + fn new_allocated(boundary: Boundary) -> Self { + Self { tag_type: TagType::Allocated, boundary } + } + + fn new_free(boundary: Boundary) -> Self { + Self { tag_type: TagType::Free, boundary } + } + + fn new_span(boundary: Boundary) -> Self { + Self { tag_type: TagType::Span, boundary } + } +} + +// impl fmt::Debug for Tag { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// write!( +// f, +// "Tag({:?} {}..{} (size: {}))", +// self.tag_type, +// self.boundary.start, +// self.boundary.start + self.boundary.size, +// self.boundary.size +// )?; +// Ok(()) +// } +// } + +#[derive(Debug)] +struct TagItem { + tag: Tag, + next: *mut TagItem, + prev: *mut TagItem, +} + +impl TagItem { #[cfg(test)] - fn new(boundary: Boundary) -> Self { - Self { boundary, next: null_mut(), prev: null_mut() } + fn new_allocated(boundary: Boundary) -> Self { + Self { tag: Tag::new_allocated(boundary), next: null_mut(), prev: null_mut() } } + // #[cfg(test)] + // fn new_free(boundary: Boundary) -> Self { + // Self { tag: Tag::new_free(boundary), next: null_mut(), prev: null_mut() } + // } + + // #[cfg(test)] + // fn new_span(boundary: Boundary) -> Self { + // Self { tag: Tag::new_span(boundary), next: null_mut(), prev: null_mut() } + // } + fn clear_links(&mut self) { self.next = null_mut(); self.prev = null_mut(); @@ -54,7 +131,7 @@ impl Tag { /// Stack of tags, useful for freelist struct TagStack { - tags: *mut Tag, + tags: *mut TagItem, } impl TagStack { @@ -62,7 +139,7 @@ impl TagStack { Self { tags: null_mut() } } - fn push(&mut self, tag: &mut Tag) { + fn push(&mut self, tag: &mut TagItem) { if self.tags.is_null() { self.tags = tag; } else { @@ -72,20 +149,19 @@ impl TagStack { } } - fn pop(&mut self) -> *mut Tag { + fn pop(&mut self) -> *mut TagItem { if let Some(tag) = unsafe { self.tags.as_mut() } { self.tags = tag.next; if let Some(next_tag) = unsafe { self.tags.as_mut() } { next_tag.prev = null_mut(); } tag.clear_links(); - tag as *mut Tag + tag as *mut TagItem } else { null_mut() } } - #[cfg(test)] fn len(&self) -> usize { let mut n = 0; let mut free_tag = self.tags; @@ -99,7 +175,7 @@ impl TagStack { /// Ordered list of tags (by Tag::start) struct TagList { - tags: *mut Tag, + tags: *mut TagItem, } impl TagList { @@ -107,37 +183,36 @@ impl TagList { Self { tags: null_mut() } } - // TODO add support for spans. ATM this is a simple linked list that assumes no overlaps. - fn push(&mut self, new_tag: &mut Tag) { + // ATM this is a simple linked list that assumes no overlaps. + fn push(&mut self, new_tag: &mut TagItem) { if self.tags.is_null() { self.tags = new_tag; } else { - let mut curr_tag = self.tags; - while let Some(tag) = unsafe { curr_tag.as_mut() } { - if tag.boundary.start > new_tag.boundary.start { + let mut curr_tag_item = self.tags; + while let Some(item) = unsafe { curr_tag_item.as_mut() } { + if item.tag.boundary.start > new_tag.tag.boundary.start { // Insert before tag - if let Some(prev_tag) = unsafe { tag.prev.as_mut() } { + if let Some(prev_tag) = unsafe { item.prev.as_mut() } { prev_tag.next = new_tag; } else { // Inserting as first tag self.tags = new_tag; } - new_tag.next = tag; - tag.prev = new_tag; + new_tag.next = item; + item.prev = new_tag; return; } - if tag.next.is_null() { + if item.next.is_null() { // Inserting as last tag - new_tag.prev = tag; - tag.next = new_tag; + new_tag.prev = item; + item.next = new_tag; return; } - curr_tag = tag.next; + curr_tag_item = item.next; } } } - #[cfg(test)] fn len(&self) -> usize { let mut n = 0; let mut curr_tag = self.tags; @@ -149,12 +224,12 @@ impl TagList { } #[cfg(test)] - fn boundaries_iter(&self) -> impl Iterator + '_ { - let mut curr_tag = self.tags; + fn tags_iter(&self) -> impl Iterator + '_ { + let mut curr_tag_item = self.tags; core::iter::from_fn(move || { - if let Some(tag) = unsafe { curr_tag.as_ref() } { - curr_tag = tag.next; - return Some(tag.boundary); + if let Some(item) = unsafe { curr_tag_item.as_ref() } { + curr_tag_item = item.next; + return Some(item.tag); } else { return None; } @@ -191,9 +266,10 @@ impl TagList { pub struct Arena { _name: &'static str, - _quantum: usize, + quantum: usize, used_tags: TagList, free_tags: TagStack, + // TODO Add hashtable for allocated tags - makes it faster when freeing, given only an address } impl Arena { @@ -205,9 +281,14 @@ impl Arena { quantum: usize, static_range: VirtRange, ) -> Self { - let tags_addr = unsafe { &mut *(static_range.start() as *mut Tag) }; + let tags_addr = unsafe { &mut *(static_range.start() as *mut TagItem) }; let tags = unsafe { slice::from_raw_parts_mut(tags_addr, static_range.size()) }; + println!( + "Arena::new_with_static_range name:{} base:{:x} size:{:x} quantum:{:x}", + name, base, size, quantum + ); + Self::new_with_tags(name, base, size, quantum, tags) } @@ -218,51 +299,210 @@ impl Arena { base: usize, size: usize, quantum: usize, - free_tags: &mut [Tag], + free_tags: &mut [TagItem], ) -> Self { assert_eq!(base % quantum, 0); assert_eq!(size % quantum, 0); - let end = base.checked_add(size).expect("Arena::new_with_tags base+end out of bounds"); + assert!(base.checked_add(size).is_some()); let mut arena = Self { _name: name, - _quantum: quantum, + quantum: quantum, used_tags: TagList::new(), free_tags: TagStack::new(), }; arena.add_free_tags(free_tags); - - // Use tags to indicate the unusable boundaries - if base > 0 { - let boundary = Boundary::new(0, base).expect("invalid boundary"); - let tag = unsafe { - arena.free_tags.pop().as_mut().expect("Arena::new_with_tags no free tags") - }; - tag.boundary = boundary; - arena.used_tags.push(tag); - } - if end <= usize::MAX { - let boundary = Boundary::new(end, usize::MAX - end).expect("invalid boundary"); - let tag = unsafe { - arena.free_tags.pop().as_mut().expect("Arena::new_with_tags no free tags") - }; - tag.boundary = boundary; - arena.used_tags.push(tag); - } - + arena.add_span(base, size); arena } - fn add_free_tags(&mut self, tags: &mut [Tag]) { + pub fn add_span(&mut self, base: usize, size: usize) { + self.used_tags.push({ + let item = unsafe { self.free_tags.pop().as_mut().expect("no free tags") }; + item.tag = Tag::new_span(Boundary::new_unchecked(base, size)); + item + }); + self.used_tags.push({ + let item = unsafe { self.free_tags.pop().as_mut().expect("no free tags") }; + item.tag = Tag::new_free(Boundary::new_unchecked(base, size)); + item + }); + } + + fn add_free_tags(&mut self, tags: &mut [TagItem]) { for tag in tags { tag.clear_links(); self.free_tags.push(tag); } } + pub fn alloc(&mut self, size: usize) -> *mut u8 { + let boundary = self.alloc_segment(size); + if boundary.is_ok() { + // TODO Register in allocation hashtable + boundary.unwrap().start as *mut u8 + } else { + null_mut() + } + } + + pub fn free(&mut self, addr: *mut u8) { + // TODO Look up in allocation hashtable + panic!("can't free before allocation hashtable set up"); + } + + /// Allocate a segment, returned as a boundary + fn alloc_segment(&mut self, size: usize) -> Result { + println!("alloc_segment size: {}", size); + + // Round size up to a multiple of quantum + let size = { + let rem = size % self.quantum; + if rem == 0 { + size + } else { + size + (self.quantum - rem) + } + }; + + // Find the first free tag that's large enough + let mut curr_item = self.used_tags.tags; + while let Some(item) = unsafe { curr_item.as_mut() } { + if item.tag.tag_type == TagType::Free && item.tag.boundary.size >= size { + // Mark this tag as allocated, and if there's any left over space, create and insert a new tag + item.tag.tag_type = TagType::Allocated; + if item.tag.boundary.size > size { + // Work out the size of the new free item, and change the size of the current, now allocated, item + let remainder = item.tag.boundary.size - size; + item.tag.boundary.size = size; + + let new_item = unsafe { self.free_tags.pop().as_mut().expect("no free tags") }; + new_item.tag = Tag::new_free(Boundary::new_unchecked( + item.tag.boundary.start + size, + remainder, + )); + + // Insert new_item after item + new_item.next = item.next; + new_item.prev = item; + item.next = new_item; + if !new_item.next.is_null() { + unsafe { (*new_item.next).prev = new_item }; + } + } + return Ok(item.tag.boundary); + } + curr_item = item.next; + } + Err(AllocError::NoSpace) + } + + // Free addr. We need to know size in case the previous allocation was + // merged with others around it. + // TODO Error on precondition fail + // TODO Use hashmap if available + // TODO Return Result + fn free_segment(&mut self, boundary: Boundary) { + // Need to manually scan the used tags + let start = boundary.start as usize; + let end = boundary.end(); + let mut curr_item = self.used_tags.tags; + while let Some(item) = unsafe { curr_item.as_mut() } { + if item.tag.boundary.start <= start && item.tag.boundary.end() <= end { + break; + } + curr_item = item.next; + } + + if curr_item.is_null() { + // TODO Return error + return; + } + + // Found tag to free. Cases: + // Allocated segment encloses segment exactly: + // - If segment to the left is a span and segment to the right is a span or doesn't exist, then change segment to free + // - If segment to the left is free and segment to the right is a span or doesn't exist, then change left segment size + // Allocated segment encloses segment to free with space to the left and right: + // - Split existing segment, create new tag for free segment, new tag for allocated segment to the right + // Allocated segment starts before but ends at same point of segment to free: + // - If the segment to the right is a span, insert a new free segment. + // - If the segment to the right is free, merge by changing start of that segment. + // Allocated segment starts at same point of segment to free but ends after; + // - If the segment to the left is a span, insert a new free segment. + // - If the segment to the left is free, merge by changing end of that segment. + } + + #[cfg(test)] + fn tags_iter(&self) -> impl Iterator + '_ { + self.used_tags.tags_iter() + } + + /// Checks that all invariants are correct. #[cfg(test)] - fn boundaries_iter(&self) -> impl Iterator + '_ { - self.used_tags.boundaries_iter() + fn assert_tags_are_consistent(&self) { + // There must be at least 2 tags + debug_assert!(self.used_tags.len() >= 2); + + // Tags must be in order, without gaps + let mut last_tag: Option = None; + let mut last_span: Option = None; + let mut last_span_total = 0; + for (i, tag) in self.tags_iter().enumerate() { + debug_assert!(tag.boundary.size > 0); + + if i == 0 { + debug_assert_eq!(tag.tag_type, TagType::Span); + debug_assert!(last_tag.is_none()); + debug_assert!(last_span.is_none()); + debug_assert_eq!(last_span_total, 0); + } else { + debug_assert!(last_tag.is_some()); + debug_assert!(last_span.is_some()); + + // Tags should be ordered + let last_tag = last_tag.unwrap(); + let out_of_order = (last_tag.tag_type == TagType::Span + && tag.boundary.start >= last_tag.boundary.start) + || (last_tag.tag_type != TagType::Span + && tag.boundary.start > last_tag.boundary.start); + debug_assert!( + out_of_order, + "Tags out of order: tag{}: {:?}, tag{}: {:?}", + i - 1, + last_tag, + i, + tag, + ); + } + + match tag.tag_type { + TagType::Span => { + // Spans must not overlap + if last_span.is_some() { + debug_assert_eq!(last_span_total, last_span.unwrap().boundary.size); + } + last_span = Some(tag); + } + TagType::Allocated => { + last_span_total += tag.boundary.size; + // First tag after span should have same start as span + if last_tag.is_some_and(|t| t.tag_type == TagType::Span) { + debug_assert_eq!(tag.boundary.start, last_tag.unwrap().boundary.start); + } + } + TagType::Free => { + last_span_total += tag.boundary.size; + // First tag after span should have same start as span + if last_tag.is_some_and(|t| t.tag_type == TagType::Span) { + debug_assert_eq!(tag.boundary.start, last_tag.unwrap().boundary.start); + } + // Free tag must be last in span + debug_assert_eq!(last_span_total, last_span.unwrap().boundary.size); + } + } + last_tag = Some(tag); + } } } @@ -272,6 +512,12 @@ mod tests { use super::*; + #[test] + fn ensure_sizes() { + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 40); + } + #[test] fn test_boundary() { assert!(Boundary::new(10, 1).is_ok()); @@ -312,10 +558,9 @@ mod tests { #[test] fn test_tagstack() { - assert_eq!(size_of::(), 32); let mut page = Page4K([0; 4096]); - const NUM_TAGS: usize = size_of::() / size_of::(); - let tags = unsafe { &mut *(&mut page as *mut Page4K as *mut [Tag; NUM_TAGS]) }; + const NUM_TAGS: usize = size_of::() / size_of::(); + let tags = unsafe { &mut *(&mut page as *mut Page4K as *mut [TagItem; NUM_TAGS]) }; let mut tag_stack = TagStack::new(); assert_eq!(tag_stack.len(), 0); @@ -329,64 +574,140 @@ mod tests { fn test_taglist() { let mut list = TagList::new(); assert_eq!(list.len(), 0); - assert_eq!(list.boundaries_iter().collect::>(), []); + assert_eq!(list.tags_iter().collect::>(), []); - let mut tag1 = Tag::new(Boundary::new(100, 100).unwrap()); + let mut tag1 = TagItem::new_allocated(Boundary::new(100, 100).unwrap()); list.push(&mut tag1); assert_eq!(list.len(), 1); assert_eq!( - list.boundaries_iter().collect::>(), - [Boundary::new(100, 100).unwrap()] + list.tags_iter().collect::>(), + [Tag::new_allocated(Boundary::new(100, 100).unwrap())] ); // Insert new at end - let mut tag2 = Tag::new(Boundary::new(500, 100).unwrap()); + let mut tag2 = TagItem::new_allocated(Boundary::new(500, 100).unwrap()); list.push(&mut tag2); assert_eq!(list.len(), 2); assert_eq!( - list.boundaries_iter().collect::>(), - [Boundary::new(100, 100).unwrap(), Boundary::new(500, 100).unwrap()] + list.tags_iter().collect::>(), + [ + Tag::new_allocated(Boundary::new(100, 100).unwrap()), + Tag::new_allocated(Boundary::new(500, 100).unwrap()) + ] ); // Insert new at start - let mut tag3 = Tag::new(Boundary::new(0, 100).unwrap()); + let mut tag3 = TagItem::new_allocated(Boundary::new(0, 100).unwrap()); list.push(&mut tag3); assert_eq!(list.len(), 3); assert_eq!( - list.boundaries_iter().collect::>(), + list.tags_iter().collect::>(), [ - Boundary::new(0, 100).unwrap(), - Boundary::new(100, 100).unwrap(), - Boundary::new(500, 100).unwrap() + Tag::new_allocated(Boundary::new(0, 100).unwrap()), + Tag::new_allocated(Boundary::new(100, 100).unwrap()), + Tag::new_allocated(Boundary::new(500, 100).unwrap()) ] ); // Insert new in middle - let mut tag4 = Tag::new(Boundary::new(200, 100).unwrap()); + let mut tag4 = TagItem::new_allocated(Boundary::new(200, 100).unwrap()); list.push(&mut tag4); assert_eq!(list.len(), 4); assert_eq!( - list.boundaries_iter().collect::>(), + list.tags_iter().collect::>(), [ - Boundary::new(0, 100).unwrap(), - Boundary::new(100, 100).unwrap(), - Boundary::new(200, 100).unwrap(), - Boundary::new(500, 100).unwrap() + Tag::new_allocated(Boundary::new(0, 100).unwrap()), + Tag::new_allocated(Boundary::new(100, 100).unwrap()), + Tag::new_allocated(Boundary::new(200, 100).unwrap()), + Tag::new_allocated(Boundary::new(500, 100).unwrap()) ] ); } - #[test] - fn test_arena() { - assert_eq!(size_of::(), 32); + fn create_arena_with_static_tags( + name: &'static str, + base: usize, + size: usize, + quantum: usize, + ) -> Arena { let mut page = Page4K([0; 4096]); - const NUM_TAGS: usize = size_of::() / size_of::(); - let tags = unsafe { &mut *(&mut page as *mut Page4K as *mut [Tag; NUM_TAGS]) }; - let arena = Arena::new_with_tags("test", 4096, 8192, 4096, tags); + const NUM_TAGS: usize = size_of::() / size_of::(); + let tags = unsafe { &mut *(&mut page as *mut Page4K as *mut [TagItem; NUM_TAGS]) }; + Arena::new_with_tags(name, base, size, quantum, tags) + } - assert_eq!( - arena.boundaries_iter().collect::>(), - [Boundary::new(0, 4096).unwrap(), Boundary::new(12288, usize::MAX - 12288).unwrap()] + fn assert_tags_eq(arena: &Arena, expected: &[Tag]) { + arena.assert_tags_are_consistent(); + let actual_tags = arena.tags_iter().collect::>(); + assert_eq!(actual_tags, expected, "arena tag mismatch"); + } + + #[test] + fn test_arena_create() { + let arena = create_arena_with_static_tags("test", 4096, 4096 * 20, 4096); + + assert_tags_eq( + &arena, + &[ + Tag::new_span(Boundary::new(4096, 4096 * 20).unwrap()), + Tag::new_free(Boundary::new(4096, 4096 * 20).unwrap()), + ], ); } + + #[test] + fn test_arena_alloc() { + let mut arena = create_arena_with_static_tags("test", 4096, 4096 * 20, 4096); + + arena.alloc(4096 * 2); + + assert_tags_eq( + &arena, + &[ + Tag::new_span(Boundary::new(4096, 4096 * 20).unwrap()), + Tag::new_allocated(Boundary::new(4096, 4096 * 2).unwrap()), + Tag::new_free(Boundary::new(4096 * 3, 4096 * 18).unwrap()), + ], + ); + } + + #[test] + fn test_arena_alloc_rounds_if_wrong_granule() { + let mut arena = create_arena_with_static_tags("test", 4096, 4096 * 20, 4096); + let a = arena.alloc_segment(1024); + assert_eq!(a.unwrap().size, 4096); + } + + #[test] + fn test_arena_free() { + let mut arena = create_arena_with_static_tags("test", 4096, 4096 * 20, 4096); + + // We need to test each case where we're freeing by scanning the tags linearly. + // To do this we run through each case (comments from the `free` function) + + // Allocated segment encloses segment exactly: + // - If segment to the left is a span and segment to the right is a span or doesn't exist, then change segment to free + let a = arena.alloc(4096); + assert_tags_eq( + &arena, + &[ + Tag::new(TagType::Span, Boundary::new(4096, 4096 * 20).unwrap()), + Tag::new(TagType::Allocated, Boundary::new(4096, 4096).unwrap()), + Tag::new(TagType::Free, Boundary::new(4096 * 2, 4096 * 19).unwrap()), + ], + ); + arena.free(a); + + // - If segment to the left is free and segment to the right is a span, or doesn't exist then change left segment size + + // Allocated segment encloses segment to free with space to the left and right: + // - Split existing segment, create new tag for free segment, new tag for allocated segment to the right + + // Allocated segment starts before but ends at same point of segment to free: + // - If the segment to the right is a span, insert a new free segment. + // - If the segment to the right is free, merge by changing start of that segment. + // Allocated segment starts at same point of segment to free but ends after; + // - If the segment to the left is a span, insert a new free segment. + // - If the segment to the left is free, merge by changing end of that segment. + } }