FIXED_ARENA_DEFAULT_ALIGNMENT :: 16; FIXED_ARENA_HEADER_BYTES :: size_of(FixedArena); FixedArena :: struct { memory: []u8; cursor: s64; allocator: Allocator; } fixed_arena_alloc :: (arena: *FixedArena, byte_count: s64) -> *void { if !is_valid(arena) { log_error("[Basic/FixedArena] Attempting to allocate from an invalid or null arena: %!\n", arena); return null; } if byte_count < 0 { log_error("[Basic/FixedArena] Arena %: negative allocation size %.\n", arena, byte_count); return null; } // returned pointer is deliberately unaligned for size 0 if byte_count == 0 return arena.memory.data + arena.cursor; capacity := arena.memory.count; result, aligned := fixed_arena_next_start(arena); if aligned > capacity || byte_count > capacity - aligned { log_error("[Basic/FixedArena] Attempt to allocate, but Arena % is full! (Allocation size %; alignment %; capacity %; already allocated %.)\n", arena, byte_count, FIXED_ARENA_DEFAULT_ALIGNMENT, capacity, arena.cursor); return null; } arena.cursor = aligned + byte_count; return result; } // Resizes a block, extending it where it sits when it is the most recent allocation. // Array growth is the dominant caller of RESIZE, so that is the common path fixed_arena_resize :: (arena: *FixedArena, old_memory: *void, old_size: s64, requested_size: s64) -> *void { if !is_valid(arena) { log_error("[Basic/FixedArena] Attempting to resize against an invalid or null arena: %!\n", arena); return null; } if requested_size < 0 { log_error("[Basic/FixedArena] Arena %: negative resize size %.\n", arena, requested_size); return null; } if old_memory && old_size > 0 { old_start := cast(*u8)old_memory; old_end := old_start + old_size; // Check that the last allocation is the same as the one we want to resize: if old_start >= fixed_arena_start(arena) && old_end == arena.memory.data + arena.cursor { old_offset := cast(s64)(old_start - arena.memory.data); if requested_size <= arena.memory.count - old_offset { arena.cursor = old_offset + requested_size; // shrinking reclaims, growing extends return old_memory; } } } // Fallback: result := fixed_arena_alloc(arena, requested_size); // A resize may legitimately arrive with a old_memory that is null size_to_copy := min(old_size, requested_size); if result && old_memory && size_to_copy > 0 { memcpy(result, old_memory, size_to_copy); } return result; } fixed_arena_allocator_proc :: (mode: Allocator_Mode, requested_size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void { arena: *FixedArena = xx allocator_data; if #complete mode == { case .FREE; return null; case .RESIZE; return fixed_arena_resize(arena, old_memory, old_size, requested_size); case .ALLOCATE; return fixed_arena_alloc(arena, requested_size); case .STARTUP; #through; case .SHUTDOWN; return null; case .THREAD_START; #through; case .THREAD_STOP; return null; case .CREATE_HEAP; #through; case .DESTROY_HEAP; return null; case .IS_THIS_YOURS; if !is_valid(arena) return null; if old_memory >= fixed_arena_start(arena) && old_memory < arena.memory.data + arena.cursor { return cast(*void)true; } return null; case .CAPS; if old_memory { (cast(*string)old_memory).* = "modules/Basic fixed_arena_allocator_proc"; } return cast(*void)(Allocator_Caps.IS_THIS_YOURS | .HINT_I_AM_A_FAST_BUMP_ALLOCATOR); } } FIXED_ARENA_MINIMUM_SIZE :: size_of(FixedArena) + FIXED_ARENA_DEFAULT_ALIGNMENT; fixed_arena_init :: (arena: *FixedArena, memory: []u8, allocator := Allocator.{}) -> bool { if !arena { log_error("[Basic/FixedArena] Attempting to initialize a null arena!\n"); return false; } if memory.data == null || memory.count < FIXED_ARENA_MINIMUM_SIZE { log_error("[Basic/FixedArena] Cannot initialize an arena over a % byte block; the minimum is %.\n", memory.count, FIXED_ARENA_MINIMUM_SIZE); return false; } arena.memory = memory; arena.cursor = FIXED_ARENA_HEADER_BYTES; arena.allocator = allocator; return true; } bootstrap_fixed_arena :: (size: s64) -> *FixedArena { if size < FIXED_ARENA_MINIMUM_SIZE { log_error("[Basic/FixedArena] Requested size % is below the minimum of % bytes (a % byte header plus one % byte aligned allocation).\n", size, FIXED_ARENA_MINIMUM_SIZE, size_of(FixedArena), FIXED_ARENA_DEFAULT_ALIGNMENT); return null; } memory := NewArray(size, u8, false); if memory.data == null { log_error("[Basic/FixedArena] Failed to allocate % bytes for a new arena.\n", size); return null; } result := cast(*FixedArena)memory.data; result.memory = memory; result.cursor = FIXED_ARENA_HEADER_BYTES; result.allocator = context.allocator; return result; } delete_fixed_arena :: (arena: *FixedArena) { if !arena return; memory := arena.memory; allocator := arena.allocator; if memory.data == null return; if allocator.proc == null return; free(memory.data,, allocator); } get_allocator :: (arena: *FixedArena) -> Allocator #expand { return .{fixed_arena_allocator_proc, arena}; } fixed_arena_start :: (arena: *FixedArena) -> *u8 #expand { return arena.memory.data + FIXED_ARENA_HEADER_BYTES; } is_valid :: (arena: *FixedArena) -> bool { if !arena return false; return arena.memory.data != null && arena.memory.count >= FIXED_ARENA_HEADER_BYTES && arena.cursor >= 0 && arena.cursor <= arena.memory.count; } fixed_arena_reset :: (arena: *FixedArena) { if !is_valid(arena) return; arena.cursor = FIXED_ARENA_HEADER_BYTES; } push_arena :: (arena: *FixedArena) #expand #no_debug { assert(is_valid(arena)); old_allocator := context.allocator; context.allocator = get_allocator(arena); `defer context.allocator = old_allocator; } auto_reset :: (arena: *FixedArena) #expand #no_debug { __mark := get_fixed_arena_state(arena); `defer restore_fixed_arena_state(arena, __mark); } // The largest byte_count the next fixed_arena_alloc can satisfy. fixed_arena_available_bytes :: (arena: *FixedArena) -> s64 { if !is_valid(arena) return 0; start, offset := fixed_arena_next_start(arena); if offset >= arena.memory.count return 0; return arena.memory.count - offset; } #scope_module // Where the next aligned allocation would begin fixed_arena_next_start :: (arena: *FixedArena) -> (start: *u8, offset: s64) #expand { base := arena.memory.data; start := Align(base + arena.cursor, FIXED_ARENA_DEFAULT_ALIGNMENT); return start, cast(s64)(start - base); } Fixed_Arena_State :: struct { starting_arena : *FixedArena; starting_point : s64; } get_fixed_arena_state :: inline (arena: *FixedArena) -> Fixed_Arena_State { assert(arena != null); state: Fixed_Arena_State = ---; state.starting_arena = arena; state.starting_point = arena.cursor; return state; } restore_fixed_arena_state :: inline (arena: *FixedArena, state: Fixed_Arena_State) { assert(arena == state.starting_arena); assert(is_valid(state.starting_arena)); state.starting_arena.cursor = state.starting_point; }