mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 07:38:48 +00:00
[mem]: Document mutex, rollback stack and tracking allocators
This commit is contained in:
@@ -3,16 +3,31 @@ package mem
|
|||||||
|
|
||||||
import "core:sync"
|
import "core:sync"
|
||||||
|
|
||||||
|
/*
|
||||||
|
The data for mutex allocator.
|
||||||
|
*/
|
||||||
Mutex_Allocator :: struct {
|
Mutex_Allocator :: struct {
|
||||||
backing: Allocator,
|
backing: Allocator,
|
||||||
mutex: sync.Mutex,
|
mutex: sync.Mutex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialize the mutex allocator.
|
||||||
|
|
||||||
|
This procedure initializes the mutex allocator using `backin_allocator` as the
|
||||||
|
allocator that will be used to pass all allocation requests through.
|
||||||
|
*/
|
||||||
mutex_allocator_init :: proc(m: ^Mutex_Allocator, backing_allocator: Allocator) {
|
mutex_allocator_init :: proc(m: ^Mutex_Allocator, backing_allocator: Allocator) {
|
||||||
m.backing = backing_allocator
|
m.backing = backing_allocator
|
||||||
m.mutex = {}
|
m.mutex = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mutex allocator.
|
||||||
|
|
||||||
|
The mutex allocator is a wrapper for allocators that is used to serialize all
|
||||||
|
allocator requests across multiple threads.
|
||||||
|
*/
|
||||||
@(require_results)
|
@(require_results)
|
||||||
mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator {
|
mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator {
|
||||||
return Allocator{
|
return Allocator{
|
||||||
|
|||||||
+30
-16
@@ -4,68 +4,82 @@ import "base:builtin"
|
|||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `any` type.
|
Memory layout of the `any` type.
|
||||||
*/
|
*/
|
||||||
Raw_Any :: runtime.Raw_Any
|
Raw_Any :: runtime.Raw_Any
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `string` type.
|
Memory layout of the `string` type.
|
||||||
*/
|
*/
|
||||||
Raw_String :: runtime.Raw_String
|
Raw_String :: runtime.Raw_String
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `cstring` type.
|
Memory layout of the `cstring` type.
|
||||||
*/
|
*/
|
||||||
Raw_Cstring :: runtime.Raw_Cstring
|
Raw_Cstring :: runtime.Raw_Cstring
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of `[]T` types.
|
Memory layout of `[]T` types.
|
||||||
*/
|
*/
|
||||||
Raw_Slice :: runtime.Raw_Slice
|
Raw_Slice :: runtime.Raw_Slice
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of `[dynamic]T` types.
|
Memory layout of `[dynamic]T` types.
|
||||||
*/
|
*/
|
||||||
Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
|
Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of `map[K]V` types.
|
Memory layout of `map[K]V` types.
|
||||||
*/
|
*/
|
||||||
Raw_Map :: runtime.Raw_Map
|
Raw_Map :: runtime.Raw_Map
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of `#soa []T` types.
|
Memory layout of `#soa []T` types.
|
||||||
*/
|
*/
|
||||||
Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer
|
Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `complex32` type.
|
Memory layout of the `complex32` type.
|
||||||
*/
|
*/
|
||||||
Raw_Complex32 :: runtime.Raw_Complex32
|
Raw_Complex32 :: runtime.Raw_Complex32
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `complex64` type.
|
Memory layout of the `complex64` type.
|
||||||
*/
|
*/
|
||||||
Raw_Complex64 :: runtime.Raw_Complex64
|
Raw_Complex64 :: runtime.Raw_Complex64
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `complex128` type.
|
Memory layout of the `complex128` type.
|
||||||
*/
|
*/
|
||||||
Raw_Complex128 :: runtime.Raw_Complex128
|
Raw_Complex128 :: runtime.Raw_Complex128
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `quaternion64` type.
|
Memory layout of the `quaternion64` type.
|
||||||
*/
|
*/
|
||||||
Raw_Quaternion64 :: runtime.Raw_Quaternion64
|
Raw_Quaternion64 :: runtime.Raw_Quaternion64
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `quaternion128` type.
|
Memory layout of the `quaternion128` type.
|
||||||
*/
|
*/
|
||||||
Raw_Quaternion128 :: runtime.Raw_Quaternion128
|
Raw_Quaternion128 :: runtime.Raw_Quaternion128
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `quaternion256` type.
|
Memory layout of the `quaternion256` type.
|
||||||
*/
|
*/
|
||||||
Raw_Quaternion256 :: runtime.Raw_Quaternion256
|
Raw_Quaternion256 :: runtime.Raw_Quaternion256
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `quaternion64` type.
|
Memory layout of the `quaternion64` type.
|
||||||
*/
|
*/
|
||||||
Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar
|
Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `quaternion128` type.
|
Memory layout of the `quaternion128` type.
|
||||||
*/
|
*/
|
||||||
Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar
|
Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Mamory layout of the `quaternion256` type.
|
Memory layout of the `quaternion256` type.
|
||||||
*/
|
*/
|
||||||
Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar
|
Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,15 @@
|
|||||||
package mem
|
package mem
|
||||||
|
|
||||||
/*
|
|
||||||
The Rollback Stack Allocator was designed for the test runner to be fast,
|
|
||||||
able to grow, and respect the Tracking Allocator's requirement for
|
|
||||||
individual frees. It is not overly concerned with fragmentation, however.
|
|
||||||
|
|
||||||
It has support for expansion when configured with a block allocator and
|
|
||||||
limited support for out-of-order frees.
|
|
||||||
|
|
||||||
Allocation has constant-time best and usual case performance.
|
|
||||||
At worst, it is linear according to the number of memory blocks.
|
|
||||||
|
|
||||||
Allocation follows a first-fit strategy when there are multiple memory
|
|
||||||
blocks.
|
|
||||||
|
|
||||||
Freeing has constant-time best and usual case performance.
|
|
||||||
At worst, it is linear according to the number of memory blocks and number
|
|
||||||
of freed items preceding the last item in a block.
|
|
||||||
|
|
||||||
Resizing has constant-time performance, if it's the last item in a block, or
|
|
||||||
the new size is smaller. Naturally, this becomes linear-time if there are
|
|
||||||
multiple blocks to search for the pointer's owning block. Otherwise, the
|
|
||||||
allocator defaults to a combined alloc & free operation internally.
|
|
||||||
|
|
||||||
Out-of-order freeing is accomplished by collapsing a run of freed items
|
|
||||||
from the last allocation backwards.
|
|
||||||
|
|
||||||
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
|
|
||||||
the requested alignment.
|
|
||||||
*/
|
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Rollback stack default block size.
|
||||||
|
*/
|
||||||
ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte
|
ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Rollback stack max head block size.
|
||||||
|
|
||||||
This limitation is due to the size of `prev_ptr`, but it is only for the
|
This limitation is due to the size of `prev_ptr`, but it is only for the
|
||||||
head block; any allocation in excess of the allocator's `block_size` is
|
head block; any allocation in excess of the allocator's `block_size` is
|
||||||
valid, so long as the block allocator can handle it.
|
valid, so long as the block allocator can handle it.
|
||||||
@@ -43,12 +19,18 @@ within is freed; they are immediately returned to the block allocator.
|
|||||||
*/
|
*/
|
||||||
ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte
|
ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte
|
||||||
|
|
||||||
|
/*
|
||||||
|
Allocation header of the rollback stack allocator.
|
||||||
|
*/
|
||||||
Rollback_Stack_Header :: bit_field u64 {
|
Rollback_Stack_Header :: bit_field u64 {
|
||||||
prev_offset: uintptr | 32,
|
prev_offset: uintptr | 32,
|
||||||
is_free: bool | 1,
|
is_free: bool | 1,
|
||||||
prev_ptr: uintptr | 31,
|
prev_ptr: uintptr | 31,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Block header of the rollback stack allocator.
|
||||||
|
*/
|
||||||
Rollback_Stack_Block :: struct {
|
Rollback_Stack_Block :: struct {
|
||||||
next_block: ^Rollback_Stack_Block,
|
next_block: ^Rollback_Stack_Block,
|
||||||
last_alloc: rawptr,
|
last_alloc: rawptr,
|
||||||
@@ -56,6 +38,9 @@ Rollback_Stack_Block :: struct {
|
|||||||
buffer: []byte,
|
buffer: []byte,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Rollback stack allocator data.
|
||||||
|
*/
|
||||||
Rollback_Stack :: struct {
|
Rollback_Stack :: struct {
|
||||||
head: ^Rollback_Stack_Block,
|
head: ^Rollback_Stack_Block,
|
||||||
block_size: int,
|
block_size: int,
|
||||||
@@ -111,6 +96,9 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free memory to a rollback stack allocator.
|
||||||
|
*/
|
||||||
@(private="file", require_results)
|
@(private="file", require_results)
|
||||||
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
|
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
|
||||||
parent, block, header := rb_find_ptr(stack, ptr) or_return
|
parent, block, header := rb_find_ptr(stack, ptr) or_return
|
||||||
@@ -129,6 +117,9 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free all memory owned by the rollback stack allocator.
|
||||||
|
*/
|
||||||
@(private="file")
|
@(private="file")
|
||||||
rb_free_all :: proc(stack: ^Rollback_Stack) {
|
rb_free_all :: proc(stack: ^Rollback_Stack) {
|
||||||
for block := stack.head.next_block; block != nil; /**/ {
|
for block := stack.head.next_block; block != nil; /**/ {
|
||||||
@@ -142,14 +133,16 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
|
|||||||
stack.head.offset = 0
|
stack.head.offset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Resize an allocation made on a rollback stack allocator.
|
||||||
|
*/
|
||||||
@(private="file", require_results)
|
@(private="file", require_results)
|
||||||
rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
|
rb_resize_non_zeroed :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
|
||||||
if ptr != nil {
|
if ptr != nil {
|
||||||
if block, _, ok := rb_find_last_alloc(stack, ptr); ok {
|
if block, _, ok := rb_find_last_alloc(stack, ptr); ok {
|
||||||
// `block.offset` should never underflow because it is contingent
|
// `block.offset` should never underflow because it is contingent
|
||||||
// on `old_size` in the first place, assuming sane arguments.
|
// on `old_size` in the first place, assuming sane arguments.
|
||||||
assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.")
|
assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.")
|
||||||
|
|
||||||
if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) {
|
if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) {
|
||||||
// Prevent singleton allocations from fragmenting by forbidding
|
// Prevent singleton allocations from fragmenting by forbidding
|
||||||
// them to shrink, removing the possibility of overflow bugs.
|
// them to shrink, removing the possibility of overflow bugs.
|
||||||
@@ -160,27 +153,26 @@ rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result = rb_alloc_non_zeroed(stack, size, alignment) or_return
|
||||||
result = rb_alloc(stack, size, alignment) or_return
|
|
||||||
runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size)
|
runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size)
|
||||||
err = rb_free(stack, ptr)
|
err = rb_free(stack, ptr)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Allocate memory using the rollback stack allocator.
|
||||||
|
*/
|
||||||
@(private="file", require_results)
|
@(private="file", require_results)
|
||||||
rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
|
rb_alloc_non_zeroed :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
|
||||||
parent: ^Rollback_Stack_Block
|
parent: ^Rollback_Stack_Block
|
||||||
for block := stack.head; /**/; block = block.next_block {
|
for block := stack.head; /**/; block = block.next_block {
|
||||||
when !ODIN_DISABLE_ASSERT {
|
when !ODIN_DISABLE_ASSERT {
|
||||||
allocated_new_block: bool
|
allocated_new_block: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
if block == nil {
|
if block == nil {
|
||||||
if stack.block_allocator.procedure == nil {
|
if stack.block_allocator.procedure == nil {
|
||||||
return nil, .Out_Of_Memory
|
return nil, .Out_Of_Memory
|
||||||
}
|
}
|
||||||
|
|
||||||
minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1
|
minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1
|
||||||
new_block_size := max(minimum_size_required, stack.block_size)
|
new_block_size := max(minimum_size_required, stack.block_size)
|
||||||
block = rb_make_block(new_block_size, stack.block_allocator) or_return
|
block = rb_make_block(new_block_size, stack.block_allocator) or_return
|
||||||
@@ -189,10 +181,8 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt
|
|||||||
allocated_new_block = true
|
allocated_new_block = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start := raw_data(block.buffer)[block.offset:]
|
start := raw_data(block.buffer)[block.offset:]
|
||||||
padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header))
|
padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header))
|
||||||
|
|
||||||
if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) {
|
if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) {
|
||||||
when !ODIN_DISABLE_ASSERT {
|
when !ODIN_DISABLE_ASSERT {
|
||||||
if allocated_new_block {
|
if allocated_new_block {
|
||||||
@@ -202,54 +192,50 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt
|
|||||||
parent = block
|
parent = block
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):])
|
header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):])
|
||||||
ptr := start[padding:]
|
ptr := start[padding:]
|
||||||
|
|
||||||
header^ = {
|
header^ = {
|
||||||
prev_offset = block.offset,
|
prev_offset = block.offset,
|
||||||
prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer),
|
prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer),
|
||||||
is_free = false,
|
is_free = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
block.last_alloc = ptr
|
block.last_alloc = ptr
|
||||||
block.offset += padding + cast(uintptr)size
|
block.offset += padding + cast(uintptr)size
|
||||||
|
|
||||||
if len(block.buffer) > stack.block_size {
|
if len(block.buffer) > stack.block_size {
|
||||||
// This block exceeds the allocator's standard block size and is considered a singleton.
|
// This block exceeds the allocator's standard block size and is considered a singleton.
|
||||||
// Prevent any further allocations on it.
|
// Prevent any further allocations on it.
|
||||||
block.offset = cast(uintptr)len(block.buffer)
|
block.offset = cast(uintptr)len(block.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
#no_bounds_check return ptr[:size], nil
|
#no_bounds_check return ptr[:size], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, .Out_Of_Memory
|
return nil, .Out_Of_Memory
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private="file", require_results)
|
@(private="file", require_results)
|
||||||
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
|
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
|
||||||
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
|
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
|
||||||
|
|
||||||
block = cast(^Rollback_Stack_Block)raw_data(buffer)
|
block = cast(^Rollback_Stack_Block)raw_data(buffer)
|
||||||
#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
|
#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialize the rollback stack allocator using a fixed backing buffer.
|
||||||
|
*/
|
||||||
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
|
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
|
||||||
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
|
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
|
||||||
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
|
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
|
||||||
|
|
||||||
block := cast(^Rollback_Stack_Block)raw_data(buffer)
|
block := cast(^Rollback_Stack_Block)raw_data(buffer)
|
||||||
block^ = {}
|
block^ = {}
|
||||||
#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
|
#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
|
||||||
|
|
||||||
stack^ = {}
|
stack^ = {}
|
||||||
stack.head = block
|
stack.head = block
|
||||||
stack.block_size = len(block.buffer)
|
stack.block_size = len(block.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialize the rollback stack alocator using a backing block allocator.
|
||||||
|
*/
|
||||||
rollback_stack_init_dynamic :: proc(
|
rollback_stack_init_dynamic :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
|
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
|
||||||
@@ -262,22 +248,25 @@ rollback_stack_init_dynamic :: proc(
|
|||||||
// size is insufficient; check only on platforms with big enough ints.
|
// size is insufficient; check only on platforms with big enough ints.
|
||||||
assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location)
|
assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location)
|
||||||
}
|
}
|
||||||
|
|
||||||
block := rb_make_block(block_size, block_allocator) or_return
|
block := rb_make_block(block_size, block_allocator) or_return
|
||||||
|
|
||||||
stack^ = {}
|
stack^ = {}
|
||||||
stack.head = block
|
stack.head = block
|
||||||
stack.block_size = block_size
|
stack.block_size = block_size
|
||||||
stack.block_allocator = block_allocator
|
stack.block_allocator = block_allocator
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialize the rollback stack.
|
||||||
|
*/
|
||||||
rollback_stack_init :: proc {
|
rollback_stack_init :: proc {
|
||||||
rollback_stack_init_buffered,
|
rollback_stack_init_buffered,
|
||||||
rollback_stack_init_dynamic,
|
rollback_stack_init_dynamic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Destroy a rollback stack.
|
||||||
|
*/
|
||||||
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
|
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
|
||||||
if stack.block_allocator.procedure != nil {
|
if stack.block_allocator.procedure != nil {
|
||||||
rb_free_all(stack)
|
rb_free_all(stack)
|
||||||
@@ -286,6 +275,37 @@ rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
|
|||||||
stack^ = {}
|
stack^ = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Rollback stack allocator.
|
||||||
|
|
||||||
|
The Rollback Stack Allocator was designed for the test runner to be fast,
|
||||||
|
able to grow, and respect the Tracking Allocator's requirement for
|
||||||
|
individual frees. It is not overly concerned with fragmentation, however.
|
||||||
|
|
||||||
|
It has support for expansion when configured with a block allocator and
|
||||||
|
limited support for out-of-order frees.
|
||||||
|
|
||||||
|
Allocation has constant-time best and usual case performance.
|
||||||
|
At worst, it is linear according to the number of memory blocks.
|
||||||
|
|
||||||
|
Allocation follows a first-fit strategy when there are multiple memory
|
||||||
|
blocks.
|
||||||
|
|
||||||
|
Freeing has constant-time best and usual case performance.
|
||||||
|
At worst, it is linear according to the number of memory blocks and number
|
||||||
|
of freed items preceding the last item in a block.
|
||||||
|
|
||||||
|
Resizing has constant-time performance, if it's the last item in a block, or
|
||||||
|
the new size is smaller. Naturally, this becomes linear-time if there are
|
||||||
|
multiple blocks to search for the pointer's owning block. Otherwise, the
|
||||||
|
allocator defaults to a combined alloc & free operation internally.
|
||||||
|
|
||||||
|
Out-of-order freeing is accomplished by collapsing a run of freed items
|
||||||
|
from the last allocation backwards.
|
||||||
|
|
||||||
|
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
|
||||||
|
the requested alignment.
|
||||||
|
*/
|
||||||
@(require_results)
|
@(require_results)
|
||||||
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
|
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
|
||||||
return Allocator {
|
return Allocator {
|
||||||
@@ -309,38 +329,31 @@ rollback_stack_allocator_proc :: proc(
|
|||||||
case .Alloc, .Alloc_Non_Zeroed:
|
case .Alloc, .Alloc_Non_Zeroed:
|
||||||
assert(size >= 0, "Size must be positive or zero.", location)
|
assert(size >= 0, "Size must be positive or zero.", location)
|
||||||
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
|
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
|
||||||
result = rb_alloc(stack, size, alignment) or_return
|
result = rb_alloc_non_zeroed(stack, size, alignment) or_return
|
||||||
|
|
||||||
if mode == .Alloc {
|
if mode == .Alloc {
|
||||||
zero_slice(result)
|
zero_slice(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .Free:
|
case .Free:
|
||||||
err = rb_free(stack, old_memory)
|
err = rb_free(stack, old_memory)
|
||||||
|
|
||||||
case .Free_All:
|
case .Free_All:
|
||||||
rb_free_all(stack)
|
rb_free_all(stack)
|
||||||
|
|
||||||
case .Resize, .Resize_Non_Zeroed:
|
case .Resize, .Resize_Non_Zeroed:
|
||||||
assert(size >= 0, "Size must be positive or zero.", location)
|
assert(size >= 0, "Size must be positive or zero.", location)
|
||||||
assert(old_size >= 0, "Old size must be positive or zero.", location)
|
assert(old_size >= 0, "Old size must be positive or zero.", location)
|
||||||
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
|
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
|
||||||
result = rb_resize(stack, old_memory, old_size, size, alignment) or_return
|
result = rb_resize_non_zeroed(stack, old_memory, old_size, size, alignment) or_return
|
||||||
|
|
||||||
#no_bounds_check if mode == .Resize && size > old_size {
|
#no_bounds_check if mode == .Resize && size > old_size {
|
||||||
zero_slice(result[old_size:])
|
zero_slice(result[old_size:])
|
||||||
}
|
}
|
||||||
|
|
||||||
case .Query_Features:
|
case .Query_Features:
|
||||||
set := (^Allocator_Mode_Set)(old_memory)
|
set := (^Allocator_Mode_Set)(old_memory)
|
||||||
if set != nil {
|
if set != nil {
|
||||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed}
|
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
case .Query_Info:
|
case .Query_Info:
|
||||||
return nil, .Mode_Not_Implemented
|
return nil, .Mode_Not_Implemented
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,50 +4,38 @@ package mem
|
|||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
import "core:sync"
|
import "core:sync"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Allocation entry for the tracking allocator.
|
||||||
|
|
||||||
|
This structure stores the data related to an allocation.
|
||||||
|
*/
|
||||||
Tracking_Allocator_Entry :: struct {
|
Tracking_Allocator_Entry :: struct {
|
||||||
memory: rawptr,
|
// Pointer to an allocated region.
|
||||||
size: int,
|
memory: rawptr,
|
||||||
|
// Size of the allocated memory region.
|
||||||
|
size: int,
|
||||||
|
// Requested alignment.
|
||||||
alignment: int,
|
alignment: int,
|
||||||
mode: Allocator_Mode,
|
// Mode of the operation.
|
||||||
err: Allocator_Error,
|
mode: Allocator_Mode,
|
||||||
|
// Error.
|
||||||
|
err: Allocator_Error,
|
||||||
|
// Location of the allocation.
|
||||||
location: runtime.Source_Code_Location,
|
location: runtime.Source_Code_Location,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Bad free entry for a tracking allocator.
|
||||||
|
*/
|
||||||
Tracking_Allocator_Bad_Free_Entry :: struct {
|
Tracking_Allocator_Bad_Free_Entry :: struct {
|
||||||
memory: rawptr,
|
// Pointer, on which free operation was called.
|
||||||
|
memory: rawptr,
|
||||||
|
// The source location of where the operation was called.
|
||||||
location: runtime.Source_Code_Location,
|
location: runtime.Source_Code_Location,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
An example of how to use the `Tracking_Allocator` to track subsequent allocations
|
Tracking allocator data.
|
||||||
in your program and report leaks and bad frees:
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
package foo
|
|
||||||
|
|
||||||
import "core:mem"
|
|
||||||
import "core:fmt"
|
|
||||||
|
|
||||||
_main :: proc() {
|
|
||||||
// do stuff
|
|
||||||
}
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
track: mem.Tracking_Allocator
|
|
||||||
mem.tracking_allocator_init(&track, context.allocator)
|
|
||||||
defer mem.tracking_allocator_destroy(&track)
|
|
||||||
context.allocator = mem.tracking_allocator(&track)
|
|
||||||
|
|
||||||
_main()
|
|
||||||
|
|
||||||
for _, leak in track.allocation_map {
|
|
||||||
fmt.printf("%v leaked %m\n", leak.location, leak.size)
|
|
||||||
}
|
|
||||||
for bad_free in track.bad_free_array {
|
|
||||||
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
Tracking_Allocator :: struct {
|
Tracking_Allocator :: struct {
|
||||||
backing: Allocator,
|
backing: Allocator,
|
||||||
@@ -63,6 +51,13 @@ Tracking_Allocator :: struct {
|
|||||||
current_memory_allocated: i64,
|
current_memory_allocated: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialize the tracking allocator.
|
||||||
|
|
||||||
|
This procedure initializes the tracking allocator `t` with a backing allocator
|
||||||
|
specified with `backing_allocator`. The `internals_allocator` will used to
|
||||||
|
allocate the tracked data.
|
||||||
|
*/
|
||||||
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
|
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
|
||||||
t.backing = backing_allocator
|
t.backing = backing_allocator
|
||||||
t.allocation_map.allocator = internals_allocator
|
t.allocation_map.allocator = internals_allocator
|
||||||
@@ -72,12 +67,22 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Destroy the tracking allocator.
|
||||||
|
*/
|
||||||
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
||||||
delete(t.allocation_map)
|
delete(t.allocation_map)
|
||||||
delete(t.bad_free_array)
|
delete(t.bad_free_array)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear only the current allocation data while keeping the totals intact.
|
/*
|
||||||
|
Clear the tracking allocator.
|
||||||
|
|
||||||
|
This procedure clears the tracked data from a tracking allocator.
|
||||||
|
|
||||||
|
**Note**: This procedure clears only the current allocation data while keeping
|
||||||
|
the totals intact.
|
||||||
|
*/
|
||||||
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
||||||
sync.mutex_lock(&t.mutex)
|
sync.mutex_lock(&t.mutex)
|
||||||
clear(&t.allocation_map)
|
clear(&t.allocation_map)
|
||||||
@@ -86,7 +91,11 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
|||||||
sync.mutex_unlock(&t.mutex)
|
sync.mutex_unlock(&t.mutex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all of a Tracking Allocator's allocation data back to zero.
|
/*
|
||||||
|
Reset the tracking allocator.
|
||||||
|
|
||||||
|
Reset all of a Tracking Allocator's allocation data back to zero.
|
||||||
|
*/
|
||||||
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
|
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
|
||||||
sync.mutex_lock(&t.mutex)
|
sync.mutex_lock(&t.mutex)
|
||||||
clear(&t.allocation_map)
|
clear(&t.allocation_map)
|
||||||
@@ -100,6 +109,39 @@ tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
|
|||||||
sync.mutex_unlock(&t.mutex)
|
sync.mutex_unlock(&t.mutex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tracking allocator.
|
||||||
|
|
||||||
|
The tracking allocator is an allocator wrapper that tracks memory allocations.
|
||||||
|
This allocator stores all the allocations in a map. Whenever a pointer that's
|
||||||
|
not inside of the map is freed, the `bad_free_array` entry is added.
|
||||||
|
|
||||||
|
An example of how to use the `Tracking_Allocator` to track subsequent allocations
|
||||||
|
in your program and report leaks and bad frees:
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import "core:mem"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
track: mem.Tracking_Allocator
|
||||||
|
mem.tracking_allocator_init(&track, context.allocator)
|
||||||
|
defer mem.tracking_allocator_destroy(&track)
|
||||||
|
context.allocator = mem.tracking_allocator(&track)
|
||||||
|
|
||||||
|
do_stuff()
|
||||||
|
|
||||||
|
for _, leak in track.allocation_map {
|
||||||
|
fmt.printf("%v leaked %m\n", leak.location, leak.size)
|
||||||
|
}
|
||||||
|
for bad_free in track.bad_free_array {
|
||||||
|
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
@(require_results)
|
@(require_results)
|
||||||
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
||||||
return Allocator{
|
return Allocator{
|
||||||
|
|||||||
Reference in New Issue
Block a user