|
@@ -9,6 +9,13 @@ Arena_Kind :: enum uint {
|
|
Buffer = 2, // Uses a fixed sized buffer.
|
|
Buffer = 2, // Uses a fixed sized buffer.
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ Arena is a generalized arena allocator that supports 3 different variants.
|
|
|
|
+
|
|
|
|
+ Growing: A linked list of `Memory_Block`s allocated with virtual memory.
|
|
|
|
+ Static: A single `Memory_Block` allocated with virtual memory.
|
|
|
|
+ Buffer: A single `Memory_Block` created from a user provided []byte.
|
|
|
|
+*/
|
|
Arena :: struct {
|
|
Arena :: struct {
|
|
kind: Arena_Kind,
|
|
kind: Arena_Kind,
|
|
curr_block: ^Memory_Block,
|
|
curr_block: ^Memory_Block,
|
|
@@ -29,6 +36,8 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+// Initialization of an `Arena` to be a `.Growing` variant.
|
|
|
|
+// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
|
|
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
|
|
arena.kind = .Growing
|
|
arena.kind = .Growing
|
|
@@ -39,6 +48,8 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+// Initialization of an `Arena` to be a `.Static` variant.
|
|
|
|
+// A static arena contains a single `Memory_Block` allocated with virtual memory.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
|
|
arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
|
|
arena.kind = .Static
|
|
arena.kind = .Static
|
|
@@ -48,6 +59,8 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEF
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Initialization of an `Arena` to be a `.Buffer` variant.
|
|
|
|
+// A buffer arena contains single `Memory_Block` created from a user provided []byte.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
|
|
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
|
|
if len(buffer) < size_of(Memory_Block) {
|
|
if len(buffer) < size_of(Memory_Block) {
|
|
@@ -71,6 +84,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Allocates memory from the provided arena.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
|
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
|
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
|
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
|
@@ -119,6 +133,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Resets the memory of a Static or Buffer arena to a specific `pos`ition (offset) and zeroes the previously used memory.
|
|
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
|
|
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
|
|
sync.mutex_guard(&arena.mutex)
|
|
sync.mutex_guard(&arena.mutex)
|
|
|
|
|
|
@@ -140,6 +155,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Frees the last memory block of a Growing Arena
|
|
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
|
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
|
if free_block := arena.curr_block; free_block != nil {
|
|
if free_block := arena.curr_block; free_block != nil {
|
|
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
|
|
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
|
|
@@ -151,6 +167,7 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
|
|
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
|
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
|
switch arena.kind {
|
|
switch arena.kind {
|
|
case .Growing:
|
|
case .Growing:
|
|
@@ -171,12 +188,19 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
|
arena.total_used = 0
|
|
arena.total_used = 0
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
|
|
|
|
+// A buffer based arena does not `delete` the provided `[]byte` bufffer.
|
|
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
|
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
|
sync.mutex_guard(&arena.mutex)
|
|
sync.mutex_guard(&arena.mutex)
|
|
- if arena.kind != .Buffer {
|
|
|
|
|
|
+ switch arena.kind {
|
|
|
|
+ case .Growing:
|
|
for arena.curr_block != nil {
|
|
for arena.curr_block != nil {
|
|
arena_growing_free_last_memory_block(arena, loc)
|
|
arena_growing_free_last_memory_block(arena, loc)
|
|
}
|
|
}
|
|
|
|
+ case .Static:
|
|
|
|
+ memory_block_dealloc(arena.curr_block)
|
|
|
|
+ case .Buffer:
|
|
|
|
+ // nothing
|
|
}
|
|
}
|
|
arena.curr_block = nil
|
|
arena.curr_block = nil
|
|
arena.total_used = 0
|
|
arena.total_used = 0
|
|
@@ -184,16 +208,19 @@ arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
|
arena.temp_count = 0
|
|
arena.temp_count = 0
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
|
arena_growing_bootstrap_new :: proc{
|
|
arena_growing_bootstrap_new :: proc{
|
|
arena_growing_bootstrap_new_by_offset,
|
|
arena_growing_bootstrap_new_by_offset,
|
|
arena_growing_bootstrap_new_by_name,
|
|
arena_growing_bootstrap_new_by_name,
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
|
|
arena_static_bootstrap_new :: proc{
|
|
arena_static_bootstrap_new :: proc{
|
|
arena_static_bootstrap_new_by_offset,
|
|
arena_static_bootstrap_new_by_offset,
|
|
arena_static_bootstrap_new_by_name,
|
|
arena_static_bootstrap_new_by_name,
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
|
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
|
bootstrap: Arena
|
|
bootstrap: Arena
|
|
@@ -209,11 +236,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
|
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
|
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
|
|
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
|
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
|
bootstrap: Arena
|
|
bootstrap: Arena
|
|
@@ -229,17 +258,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
|
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
|
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
|
|
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+// Create an `Allocator` from the provided `Arena`
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
|
|
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
|
|
return mem.Allocator{arena_allocator_proc, arena}
|
|
return mem.Allocator{arena_allocator_proc, arena}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// The allocator procedured by an `Allocator` produced by `arena_allocator`
|
|
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|
size, alignment: int,
|
|
size, alignment: int,
|
|
old_memory: rawptr, old_size: int,
|
|
old_memory: rawptr, old_size: int,
|
|
@@ -296,12 +328,15 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+// An `Arena_Temp` is a way to produce temporary watermarks to reset a arena to a previous state.
|
|
|
|
+// All uses of an `Arena_Temp` must be handled by ending them with `arena_temp_end` or ignoring them with `arena_temp_ignore`.
|
|
Arena_Temp :: struct {
|
|
Arena_Temp :: struct {
|
|
arena: ^Arena,
|
|
arena: ^Arena,
|
|
block: ^Memory_Block,
|
|
block: ^Memory_Block,
|
|
used: uint,
|
|
used: uint,
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Begins the section of temporary arena memory.
|
|
@(require_results)
|
|
@(require_results)
|
|
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
|
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
|
assert(arena != nil, "nil arena", loc)
|
|
assert(arena != nil, "nil arena", loc)
|
|
@@ -316,6 +351,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Ends the section of temporary arena memory by resetting the memory to the stored position.
|
|
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|
assert(temp.arena != nil, "nil arena", loc)
|
|
assert(temp.arena != nil, "nil arena", loc)
|
|
arena := temp.arena
|
|
arena := temp.arena
|
|
@@ -349,7 +385,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|
arena.temp_count -= 1
|
|
arena.temp_count -= 1
|
|
}
|
|
}
|
|
|
|
|
|
-// Ignore the use of a `arena_temp_begin` entirely
|
|
|
|
|
|
+// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
|
|
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|
assert(temp.arena != nil, "nil arena", loc)
|
|
assert(temp.arena != nil, "nil arena", loc)
|
|
arena := temp.arena
|
|
arena := temp.arena
|
|
@@ -359,6 +395,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|
arena.temp_count -= 1
|
|
arena.temp_count -= 1
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
|
|
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
|
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
|
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
|
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
|
}
|
|
}
|