bump_allocator.txt (2901B)
1 =============================================================================== 2 Bump Allocator 3 =============================================================================== 4 5 A bump allocator (also called an arena or region allocator) is about the 6 simplest allocator you can build: 7 8 1. You grab one big block of memory up front 9 2. hold a pointer into it 10 3. and every allocation just aligns that pointer and advances it past the 11 requested size 12 13 struct Bump { 14 start: *mut u8, 15 end: *mut u8, 16 ptr: Cell<*mut u8>, // the "bump pointer" 17 } 18 19 fn alloc(&self, layout: Layout) -> Option<*mut u8> { 20 let cur = self.ptr.get() as usize; 21 let aligned = (cur + layout.align() - 1) & !(layout.align() - 1); 22 let new = aligned.checked_add(layout.size())?; 23 if new > self.end as usize { return None; } 24 self.ptr.set(new as *mut u8); 25 Some(aligned as *mut u8) 26 } 27 28 --------------- 29 30 /// Round `addr` up to the next multiple of `align`. 31 /// `align` must be a power of two. 32 fn align_up(addr: usize, align: usize) -> usize { 33 debug_assert!(align.is_power_of_two()); 34 35 // For a power of two, `align - 1` is a mask of the low bits: 36 // align = 8 -> low_bits = 0b111 37 // An address is aligned exactly when those low bits are all zero. 38 let low_bits = align - 1; 39 40 // Clearing the low bits rounds DOWN to a multiple of align. 41 // Adding `low_bits` first turns that into rounding UP, 42 // while leaving already-aligned addresses untouched. 43 (addr + low_bits) & !low_bits 44 } 45 46 impl Bump { 47 fn alloc(&self, layout: Layout) -> Option<*mut u8> { 48 // Where the next allocation would start, as a plain integer. 49 // (Integers, not pointers, so the arithmetic below has no UB rules 50 // about staying inside one allocated object.) 51 let cursor = self.ptr.get() as usize; 52 53 // Step 1: move the cursor forward until it satisfies the 54 // alignment this type requires. Any bytes skipped are padding. 55 let object_start = align_up(cursor, layout.align()); 56 57 // Step 2: work out where the object ends. 58 // `checked_add` guards against a huge `size` wrapping usize around 59 // to a small number, which would sneak past the bounds check below. 60 let object_end = object_start.checked_add(layout.size())?; 61 62 // Step 3: does it actually fit in what's left of the chunk? 63 // `end` is one-past-the-last byte, so landing exactly on it is fine. 64 let chunk_end = self.end as usize; 65 if object_end > chunk_end { 66 return None; // chunk exhausted — caller can grab a new one 67 } 68 69 // Step 4: commit. This is the only mutation; everything above 70 // was just arithmetic that we could still walk away from. 71 self.ptr.set(object_end as *mut u8); 72 73 // The caller's data lives in [object_start, object_end). 74 Some(object_start as *mut u8) 75 } 76 }