notes

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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 }