244 lines
7.1 KiB
Plaintext
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;
|
|
} |