Unify Static_Arena and Growing_Arena into Arena

This commit is contained in:
gingerBill
2022-10-12 19:10:04 +01:00
parent 9bdbb45517
commit 6642e1fc9d
4 changed files with 302 additions and 355 deletions
+270
View File
@@ -0,0 +1,270 @@
package mem_virtual
import "core:mem"
Arena_Kind :: enum u8 {
Growing = 0, // chained memory block
Static = 1, // fixed reservation
}
Arena :: struct {
curr_block: ^Memory_Block,
total_used: uint,
total_reserved: uint,
kind: Arena_Kind,
minimum_block_size: uint,
temp_count: int,
}
STATIC_ARENA_DEFAULT_COMMIT_SIZE :: 1<<20 // 1 MiB should be enough to start with
GROWING_ARENA_DEFAULT_MINIMUM_BLOCK_SIZE :: STATIC_ARENA_DEFAULT_COMMIT_SIZE
// 1 GiB on 64-bit systems, 128 MiB on 32-bit systems by default
STATIC_ARENA_DEFAULT_RESERVE_SIZE :: 1<<30 when size_of(uintptr) == 8 else 1<<27
arena_init_growing :: proc(arena: ^Arena, reserved: uint = GROWING_ARENA_DEFAULT_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
arena.kind = .Growing
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved
return
}
arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = STATIC_ARENA_DEFAULT_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.kind = .Static
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved
return
}
arena_alloc :: proc(arena: ^Arena, min_size: int, alignment: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
align_forward_offset :: proc "contextless" (arena: ^Arena, alignment: int) -> uint #no_bounds_check {
alignment_offset := uint(0)
ptr := uintptr(arena.curr_block.base[arena.curr_block.used:])
mask := uintptr(alignment-1)
if ptr & mask != 0 {
alignment_offset = uint(alignment) - uint(ptr & mask)
}
return alignment_offset
}
assert(mem.is_power_of_two(uintptr(alignment)), "non-power of two alignment", loc)
switch arena.kind {
case .Growing:
size := uint(0)
if arena.curr_block != nil {
size = uint(min_size) + align_forward_offset(arena, alignment)
}
if arena.curr_block == nil || arena.curr_block.used + size > arena.curr_block.reserved {
size = uint(mem.align_forward_int(min_size, alignment))
arena.minimum_block_size = max(GROWING_ARENA_DEFAULT_MINIMUM_BLOCK_SIZE, arena.minimum_block_size)
block_size := max(size, arena.minimum_block_size)
new_block := memory_block_alloc(size, block_size, {}) or_return
new_block.prev = arena.curr_block
arena.curr_block = new_block
arena.total_reserved += new_block.reserved
}
data, err = alloc_from_memory_block(arena.curr_block, int(size), alignment)
if err == nil {
arena.total_used += size
}
case .Static:
if arena.curr_block == nil {
reserve_size := max(arena.minimum_block_size, STATIC_ARENA_DEFAULT_RESERVE_SIZE)
arena_init_static(arena, reserve_size, STATIC_ARENA_DEFAULT_COMMIT_SIZE) or_return
}
data, err = alloc_from_memory_block(arena.curr_block, min_size, alignment)
arena.total_used = arena.curr_block.used
}
return
}
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
if arena.curr_block != nil {
assert(arena.kind == .Static, "expected a .Static arena", loc)
prev_pos := arena.curr_block.used
arena.curr_block.used = clamp(pos, 0, arena.curr_block.reserved)
if prev_pos < pos {
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:pos-prev_pos])
}
return true
} else if pos == 0 {
return true
}
return false
}
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
if free_block := arena.curr_block; free_block != nil {
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
arena.curr_block = free_block.prev
memory_block_dealloc(free_block)
}
}
arena_free_all :: proc(arena: ^Arena) {
switch arena.kind {
case .Growing:
for arena.curr_block != nil {
arena_growing_free_last_memory_block(arena)
}
case .Static:
arena_static_reset_to(arena, 0)
}
arena.total_used = 0
arena.total_reserved = 0
}
arena_destroy :: proc(arena: ^Arena) {
arena_free_all(arena)
memory_block_dealloc(arena.curr_block)
arena.curr_block = nil
arena.total_used = 0
arena.total_reserved = 0
arena.temp_count = 0
}
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = GROWING_ARENA_DEFAULT_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
bootstrap.kind = .Growing
bootstrap.minimum_block_size = minimum_block_size
data := arena_alloc(&bootstrap, size_of(T), align_of(T)) or_return
ptr = (^T)(raw_data(data))
(^Arena)(uintptr(ptr) + offset_to_arena)^ = bootstrap
return
}
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = GROWING_ARENA_DEFAULT_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)
}
arena_growing_bootstrap_new :: proc{
arena_growing_bootstrap_new_by_offset,
arena_growing_bootstrap_new_by_name,
}
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
bootstrap.kind = .Static
bootstrap.minimum_block_size = reserved
data := arena_alloc(&bootstrap, size_of(T), align_of(T)) or_return
ptr = (^T)(raw_data(data))
(^Arena)(uintptr(ptr) + offset_to_arena)^ = bootstrap
return
}
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)
}
arena_static_bootstrap_new :: proc{
arena_static_bootstrap_new_by_offset,
arena_static_bootstrap_new_by_name,
}
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
return mem.Allocator{arena_allocator_proc, arena}
}
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,
location := #caller_location) -> (data: []byte, err: Allocator_Error) {
arena := (^Arena)(allocator_data)
switch mode {
case .Alloc:
return arena_alloc(arena, size, alignment)
case .Free:
err = .Mode_Not_Implemented
return
case .Free_All:
arena_free_all(arena)
return
case .Resize:
return mem.default_resize_bytes_align(mem.byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena), location)
case .Query_Features, .Query_Info:
err = .Mode_Not_Implemented
return
}
err = .Mode_Not_Implemented
return
}
Arena_Temp :: struct {
arena: ^Arena,
block: ^Memory_Block,
used: uint,
}
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
assert(arena != nil, "nil arena", loc)
temp.arena = arena
temp.block = arena.curr_block
if arena.curr_block != nil {
temp.used = arena.curr_block.used
}
arena.temp_count += 1
return
}
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
memory_block_found := false
for block := arena.curr_block; block != nil; block = block.prev {
if block == temp.block {
memory_block_found = true
break
}
}
if !memory_block_found {
assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc)
}
for arena.curr_block != temp.block {
arena_growing_free_last_memory_block(arena)
}
if block := arena.curr_block; block != nil {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := min(block.used-temp.used, block.reserved-block.used)
mem.zero_slice(block.base[temp.used:][:amount_to_zero])
block.used = temp.used
}
assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
arena.temp_count -= 1
}
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
}
+32 -32
View File
@@ -1,41 +1,41 @@
package mem_virtual
arena_init :: proc{
static_arena_init,
growing_arena_init,
}
// arena_init :: proc{
// static_arena_init,
// growing_arena_init,
// }
arena_temp_begin :: proc{
static_arena_temp_begin,
growing_arena_temp_begin,
}
// arena_temp_begin :: proc{
// static_arena_temp_begin,
// growing_arena_temp_begin,
// }
arena_temp_end :: proc{
static_arena_temp_end,
growing_arena_temp_end,
}
// arena_temp_end :: proc{
// static_arena_temp_end,
// growing_arena_temp_end,
// }
arena_check_temp :: proc{
static_arena_check_temp,
growing_arena_check_temp,
}
// arena_check_temp :: proc{
// static_arena_check_temp,
// growing_arena_check_temp,
// }
arena_allocator :: proc{
static_arena_allocator,
growing_arena_allocator,
}
// arena_allocator :: proc{
// static_arena_allocator,
// growing_arena_allocator,
// }
arena_alloc :: proc{
static_arena_alloc,
growing_arena_alloc,
}
// arena_alloc :: proc{
// static_arena_alloc,
// growing_arena_alloc,
// }
arena_free_all :: proc{
static_arena_free_all,
growing_arena_free_all,
}
// arena_free_all :: proc{
// static_arena_free_all,
// growing_arena_free_all,
// }
arena_destroy :: proc{
static_arena_destroy,
growing_arena_destroy,
}
// arena_destroy :: proc{
// static_arena_destroy,
// growing_arena_destroy,
// }
-170
View File
@@ -1,170 +0,0 @@
package mem_virtual
import "core:mem"
Growing_Arena :: struct {
curr_block: ^Memory_Block,
total_used: uint,
total_reserved: uint,
minimum_block_size: uint,
temp_count: int,
}
DEFAULT_MINIMUM_BLOCK_SIZE :: 1<<20 // 1 MiB should be enough
growing_arena_init :: proc(arena: ^Growing_Arena, reserved: uint = DEFAULT_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved
return
}
growing_arena_alloc :: proc(arena: ^Growing_Arena, min_size: int, alignment: int) -> (data: []byte, err: Allocator_Error) {
align_forward_offset :: proc "contextless" (arena: ^Growing_Arena, alignment: int) -> uint #no_bounds_check {
alignment_offset := uint(0)
ptr := uintptr(arena.curr_block.base[arena.curr_block.used:])
mask := uintptr(alignment-1)
if ptr & mask != 0 {
alignment_offset = uint(alignment) - uint(ptr & mask)
}
return alignment_offset
}
assert(mem.is_power_of_two(uintptr(alignment)))
size := uint(0)
if arena.curr_block != nil {
size = uint(min_size) + align_forward_offset(arena, alignment)
}
if arena.curr_block == nil || arena.curr_block.used + size > arena.curr_block.reserved {
size = uint(mem.align_forward_int(min_size, alignment))
arena.minimum_block_size = max(DEFAULT_MINIMUM_BLOCK_SIZE, arena.minimum_block_size)
block_size := max(size, arena.minimum_block_size)
new_block := memory_block_alloc(size, block_size, {}) or_return
new_block.prev = arena.curr_block
arena.curr_block = new_block
arena.total_reserved += new_block.reserved
}
data, err = alloc_from_memory_block(arena.curr_block, int(size), alignment)
if err == nil {
arena.total_used += size
}
return
}
growing_arena_free_last_memory_block :: proc(arena: ^Growing_Arena) {
free_block := arena.curr_block
arena.curr_block = free_block.prev
memory_block_dealloc(free_block)
}
growing_arena_free_all :: proc(arena: ^Growing_Arena) {
for arena.curr_block != nil {
growing_arena_free_last_memory_block(arena)
}
arena.total_used = 0
arena.total_reserved = 0
}
growing_arena_destroy :: proc(arena: ^Growing_Arena) {
growing_arena_free_all(arena)
}
growing_arena_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { bootstrap: Growing_Arena
bootstrap.minimum_block_size = minimum_block_size
data := growing_arena_alloc(&bootstrap, size_of(T), align_of(T)) or_return
ptr = (^T)(raw_data(data))
(^Growing_Arena)(uintptr(ptr) + offset_to_arena)^ = bootstrap
return
}
growing_arena_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
return growing_arena_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
}
growing_arena_bootstrap_new :: proc{
growing_arena_bootstrap_new_by_offset,
growing_arena_bootstrap_new_by_name,
}
growing_arena_allocator :: proc(arena: ^Growing_Arena) -> mem.Allocator {
return mem.Allocator{growing_arena_allocator_proc, arena}
}
growing_arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,
location := #caller_location) -> (data: []byte, err: Allocator_Error) {
arena := (^Growing_Arena)(allocator_data)
switch mode {
case .Alloc:
return growing_arena_alloc(arena, size, alignment)
case .Free:
err = .Mode_Not_Implemented
return
case .Free_All:
growing_arena_free_all(arena)
return
case .Resize:
return mem.default_resize_bytes_align(mem.byte_slice(old_memory, old_size), size, alignment, growing_arena_allocator(arena), location)
case .Query_Features, .Query_Info:
err = .Mode_Not_Implemented
return
}
err = .Mode_Not_Implemented
return
}
Growing_Arena_Temp :: struct {
arena: ^Growing_Arena,
block: ^Memory_Block,
used: uint,
}
growing_arena_temp_begin :: proc(arena: ^Growing_Arena) -> (temp: Growing_Arena_Temp) {
temp.arena = arena
temp.block = arena.curr_block
if arena.curr_block != nil {
temp.used = arena.curr_block.used
}
arena.temp_count += 1
return
}
growing_arena_temp_end :: proc(temp: Growing_Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
for arena.curr_block != temp.block {
growing_arena_free_last_memory_block(arena)
}
if block := arena.curr_block; block != nil {
assert(block.used >= temp.used, "out of order use of growing_arena_temp_end", loc)
amount_to_zero := min(block.used-temp.used, block.reserved-block.used)
mem.zero_slice(block.base[temp.used:][:amount_to_zero])
block.used = temp.used
}
assert(arena.temp_count > 0, "double-use of growing_arena_temp_end", loc)
arena.temp_count -= 1
}
growing_arena_check_temp :: proc(arena: ^Growing_Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Growing_Arena_Temp not been ended", loc)
}
-153
View File
@@ -1,153 +0,0 @@
package mem_virtual
import "core:mem"
Static_Arena :: struct {
block: ^Memory_Block,
total_used: uint,
total_reserved: uint,
minimum_block_size: uint,
temp_count: int,
}
STATIC_ARENA_DEFAULT_COMMIT_SIZE :: 1<<20 // 1 MiB should be enough to start with
// 1 GiB on 64-bit systems, 128 MiB on 32-bit systems by default
STATIC_ARENA_DEFAULT_RESERVE_SIZE :: 1<<30 when size_of(uintptr) == 8 else 1<<27
static_arena_init :: proc(arena: ^Static_Arena, reserved: uint, commit_size: uint = STATIC_ARENA_DEFAULT_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0
arena.total_reserved = arena.block.reserved
return
}
static_arena_destroy :: proc(arena: ^Static_Arena) {
memory_block_dealloc(arena.block)
arena^ = {}
}
static_arena_alloc :: proc(arena: ^Static_Arena, size: int, alignment: int) -> (data: []byte, err: Allocator_Error) {
align_forward :: #force_inline proc "contextless" (ptr: uint, align: uint) -> uint {
mask := align-1
return (ptr + mask) &~ mask
}
if arena.block == nil {
reserve_size := max(arena.minimum_block_size, STATIC_ARENA_DEFAULT_RESERVE_SIZE)
static_arena_init(arena, reserve_size, STATIC_ARENA_DEFAULT_COMMIT_SIZE) or_return
}
MINIMUM_ALIGN :: 2*align_of(uintptr)
defer arena.total_used = arena.block.used
return alloc_from_memory_block(arena.block, size, max(MINIMUM_ALIGN, alignment))
}
static_arena_reset_to :: proc(arena: ^Static_Arena, pos: uint) -> bool {
if arena.block != nil {
prev_pos := arena.block.used
arena.block.used = clamp(pos, 0, arena.block.reserved)
if prev_pos < pos {
mem.zero_slice(arena.block.base[arena.block.used:][:pos-prev_pos])
}
return true
} else if pos == 0 {
return true
}
return false
}
static_arena_free_all :: proc(arena: ^Static_Arena) {
static_arena_reset_to(arena, 0)
}
static_arena_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Static_Arena
bootstrap.minimum_block_size = reserved
data := static_arena_alloc(&bootstrap, size_of(T), align_of(T)) or_return
ptr = (^T)(raw_data(data))
(^Static_Arena)(uintptr(ptr) + offset_to_arena)^ = bootstrap
return
}
static_arena_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
return static_arena_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
}
static_arena_bootstrap_new :: proc{
static_arena_bootstrap_new_by_offset,
static_arena_bootstrap_new_by_name,
}
static_arena_allocator :: proc(arena: ^Static_Arena) -> mem.Allocator {
return mem.Allocator{static_arena_allocator_proc, arena}
}
static_arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,
location := #caller_location) -> (data: []byte, err: Allocator_Error) {
arena := (^Static_Arena)(allocator_data)
switch mode {
case .Alloc:
return static_arena_alloc(arena, size, alignment)
case .Free:
err = .Mode_Not_Implemented
return
case .Free_All:
static_arena_free_all(arena)
return
case .Resize:
return mem.default_resize_bytes_align(mem.byte_slice(old_memory, old_size), size, alignment, static_arena_allocator(arena), location)
case .Query_Features, .Query_Info:
err = .Mode_Not_Implemented
return
}
err = .Mode_Not_Implemented
return
}
Static_Arena_Temp :: struct {
arena: ^Static_Arena,
used: uint,
}
static_arena_temp_begin :: proc(arena: ^Static_Arena) -> (temp: Static_Arena_Temp) {
temp.arena = arena
temp.used = arena.block.used if arena.block != nil else 0
arena.temp_count += 1
return
}
static_arena_temp_end :: proc(temp: Static_Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
used := arena.block.used if arena.block != nil else 0
assert(temp.used >= used, "invalid Static_Arena_Temp", loc)
static_arena_reset_to(arena, temp.used)
assert(arena.temp_count > 0, "double-use of static_arena_temp_end", loc)
arena.temp_count -= 1
}
static_arena_check_temp :: proc(arena: ^Static_Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Static_Arena_Temp not been ended", loc)
}