123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- package runtime
- import "core:intrinsics"
- DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
- Memory_Block :: struct {
- prev: ^Memory_Block,
- allocator: Allocator,
- base: [^]byte,
- used: uint,
- capacity: uint,
- }
- Arena :: struct {
- backing_allocator: Allocator,
- curr_block: ^Memory_Block,
- total_used: uint,
- total_capacity: uint,
- minimum_block_size: uint,
- temp_count: uint,
- }
- @(private, require_results)
- safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) {
- z, did_overflow := intrinsics.overflow_add(x, y)
- return z, !did_overflow
- }
- @(require_results)
- memory_block_alloc :: proc(allocator: Allocator, capacity: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) {
- total_size := uint(capacity + size_of(Memory_Block))
- base_offset := uintptr(size_of(Memory_Block))
- min_alignment: int = max(16, align_of(Memory_Block))
- data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return
- block = (^Memory_Block)(raw_data(data))
- end := uintptr(raw_data(data)[len(data):])
- block.allocator = allocator
- block.base = ([^]byte)(uintptr(block) + base_offset)
- block.capacity = uint(end - uintptr(block.base))
- // Should be zeroed
- assert(block.used == 0)
- assert(block.prev == nil)
- return
- }
- memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
- if block_to_free != nil {
- allocator := block_to_free.allocator
- mem_free(block_to_free, allocator, loc)
- }
- }
- @(require_results)
- alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) {
- calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
- alignment_offset := uint(0)
- ptr := uintptr(block.base[block.used:])
- mask := alignment-1
- if ptr & mask != 0 {
- alignment_offset = uint(alignment - (ptr & mask))
- }
- return alignment_offset
- }
- if block == nil {
- return nil, .Out_Of_Memory
- }
- alignment_offset := calc_alignment_offset(block, uintptr(alignment))
- size, size_ok := safe_add(min_size, alignment_offset)
- if !size_ok {
- err = .Out_Of_Memory
- return
- }
- if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity {
- err = .Out_Of_Memory
- return
- }
- data = block.base[block.used+alignment_offset:][:min_size]
- block.used += size
- return
- }
- @(require_results)
- arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
- align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint {
- p := ptr
- modulo := p & (align-1)
- if modulo != 0 {
- p += align - modulo
- }
- return p
- }
- assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
- size := size
- if size == 0 {
- return
- }
- if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.capacity {
- size = align_forward_uint(size, alignment)
- if arena.minimum_block_size == 0 {
- arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
- }
- block_size := max(size, arena.minimum_block_size)
- if arena.backing_allocator.procedure == nil {
- arena.backing_allocator = default_allocator()
- }
- new_block := memory_block_alloc(arena.backing_allocator, block_size, loc) or_return
- new_block.prev = arena.curr_block
- arena.curr_block = new_block
- arena.total_capacity += new_block.capacity
- }
- prev_used := arena.curr_block.used
- data, err = alloc_from_memory_block(arena.curr_block, size, alignment)
- arena.total_used += arena.curr_block.used - prev_used
- return
- }
- // `arena_init` will initialize the arena with a usuable block.
- // This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary
- @(require_results)
- arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error {
- arena^ = {}
- arena.backing_allocator = backing_allocator
- arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB
- new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, loc) or_return
- arena.curr_block = new_block
- arena.total_capacity += new_block.capacity
- return nil
- }
- arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
- if free_block := arena.curr_block; free_block != nil {
- arena.curr_block = free_block.prev
- arena.total_capacity -= free_block.capacity
- memory_block_dealloc(free_block, loc)
- }
- }
- // `arena_free_all` will free all but the first memory block, and then reset the memory block
- arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
- for arena.curr_block != nil && arena.curr_block.prev != nil {
- arena_free_last_memory_block(arena, loc)
- }
- if arena.curr_block != nil {
- intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
- arena.curr_block.used = 0
- }
- arena.total_used = 0
- }
- arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
- for arena.curr_block != nil {
- free_block := arena.curr_block
- arena.curr_block = free_block.prev
- arena.total_capacity -= free_block.capacity
- memory_block_dealloc(free_block, loc)
- }
- arena.total_used = 0
- arena.total_capacity = 0
- }
- arena_allocator :: proc(arena: ^Arena) -> Allocator {
- return Allocator{arena_allocator_proc, arena}
- }
- arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
- size, alignment: int,
- old_memory: rawptr, old_size: int,
- location := #caller_location) -> (data: []byte, err: Allocator_Error) {
- arena := (^Arena)(allocator_data)
- size, alignment := uint(size), uint(alignment)
- old_size := uint(old_size)
- switch mode {
- case .Alloc, .Alloc_Non_Zeroed:
- return arena_alloc(arena, size, alignment, location)
- case .Free:
- err = .Mode_Not_Implemented
- case .Free_All:
- arena_free_all(arena, location)
- case .Resize:
- old_data := ([^]byte)(old_memory)
- switch {
- case old_data == nil:
- return arena_alloc(arena, size, alignment, location)
- case size == old_size:
- // return old memory
- data = old_data[:size]
- return
- case size == 0:
- err = .Mode_Not_Implemented
- return
- case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size:
- // shrink data in-place
- data = old_data[:size]
- return
- }
- new_memory := arena_alloc(arena, size, alignment, location) or_return
- if new_memory == nil {
- return
- }
- copy(new_memory, old_data[:old_size])
- return new_memory, nil
- case .Query_Features:
- set := (^Allocator_Mode_Set)(old_memory)
- if set != nil {
- set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features}
- }
- case .Query_Info:
- err = .Mode_Not_Implemented
- }
- return
- }
- Arena_Temp :: struct {
- arena: ^Arena,
- block: ^Memory_Block,
- used: uint,
- }
- @(require_results)
- 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) {
- if temp.arena == nil {
- assert(temp.block == nil)
- assert(temp.used == 0)
- return
- }
- arena := temp.arena
- if temp.block != nil {
- 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_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.capacity-block.used)
- intrinsics.mem_zero(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
- }
- // Ignore the use of a `arena_temp_begin` entirely
- arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
- assert(temp.arena != nil, "nil arena", loc)
- arena := temp.arena
- 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)
- }
|