mcap/modules/Basic/fixed_arena.jai

244 lines
7.1 KiB
Plaintext

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;
}