Merge pull request #4209 from flysand7/core-mem

[core/mem]: Document, refactor, reformat!
This commit is contained in:
gingerBill
2024-09-16 17:35:19 +01:00
committed by GitHub
10 changed files with 3931 additions and 1156 deletions
+7 -4
View File
@@ -118,16 +118,15 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r
DEFAULT_ALIGNMENT :: 2*align_of(rawptr) DEFAULT_ALIGNMENT :: 2*align_of(rawptr)
mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
if size == 0 { assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
return nil, nil if size == 0 || allocator.procedure == nil{
}
if allocator.procedure == nil {
return nil, nil return nil, nil
} }
return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc)
} }
mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
if size == 0 || allocator.procedure == nil { if size == 0 || allocator.procedure == nil {
return nil, nil return nil, nil
} }
@@ -135,6 +134,7 @@ mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, a
} }
mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
if size == 0 || allocator.procedure == nil { if size == 0 || allocator.procedure == nil {
return nil, nil return nil, nil
} }
@@ -174,6 +174,7 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle
} }
_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { _mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
if allocator.procedure == nil { if allocator.procedure == nil {
return nil, nil return nil, nil
} }
@@ -215,9 +216,11 @@ _mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignmen
} }
mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc) return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc)
} }
non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc)
} }
+935 -79
View File
File diff suppressed because it is too large Load Diff
+1989 -849
View File
File diff suppressed because it is too large Load Diff
+103 -23
View File
@@ -1,34 +1,114 @@
/* /*
package mem implements various types of allocators. The `mem` package implements various allocators and provides utility procedures
for dealing with memory, pointers and slices.
The documentation below describes basic concepts, applicable to the `mem`
package.
An example of how to use the `Tracking_Allocator` to track subsequent allocations ## Pointers, multipointers, and slices
in your program and report leaks and bad frees:
Example: A *pointer* is an abstraction of an *address*, a numberic value representing the
package foo location of an object in memory. That object is said to be *pointed to* by the
pointer. To obtain the address of a pointer, cast it to `uintptr`.
import "core:mem" A multipointer is a pointer that points to multiple objects. Unlike a pointer,
import "core:fmt" a multipointer can be indexed, but does not have a definite length. A slice is
a pointer that points to multiple objects equipped with the length, specifying
the amount of objects a slice points to.
_main :: proc() { When object's values are read through a pointer, that operation is called a
// do stuff *load* operation. When memory is read through a pointer, that operation is
} called a *store* operation. Both of these operations can be called a *memory
access operation*.
main :: proc() { ## Allocators
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
defer mem.tracking_allocator_destroy(&track)
context.allocator = mem.tracking_allocator(&track)
_main() In C and C++ memory models, allocations of objects in memory are typically
treated individually with a generic allocator (The `malloc` procedure). Which in
some scenarios can lead to poor cache utilization, slowdowns on individual
objects' memory management and growing complexity of the code needing to keep
track of the pointers and their lifetimes.
for _, leak in track.allocation_map { Using different kinds of *allocators* for different purposes can solve these
fmt.printf("%v leaked %m\n", leak.location, leak.size) problems. The allocators are typically optimized for specific use-cases and
} can potentially simplify the memory management code.
for bad_free in track.bad_free_array {
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) For example, in the context of making a game, having an Arena allocator could
} simplify allocations of any temporary memory, because the programmer doesn't
} have to keep track of which objects need to be freed every time they are
allocated, because at the end of every frame the whole allocator is reset to
its initial state and all objects are freed at once.
The allocators have different kinds of restrictions on object lifetimes, sizes,
alignment and can be a significant gain, if used properly. Odin supports
allocators on a language level.
Operations such as `new`, `free` and `delete` by default will use
`context.allocator`, which can be overridden by the user. When an override
happens all called procedures will inherit the new context and use the same
allocator.
We will define one concept to simplify the description of some allocator-related
procedures, which is ownership. If the memory was allocated via a specific
allocator, that allocator is said to be the *owner* of that memory region. To
note, unlike Rust, in Odin the memory ownership model is not strict.
## Alignment
An address is said to be *aligned to `N` bytes*, if the addresses's numeric
value is divisible by `N`. The number `N` in this case can be referred to as
the *alignment boundary*. Typically an alignment is a power of two integer
value.
A *natural alignment* of an object is typically equal to its size. For example
a 16 bit integer has a natural alignment of 2 bytes. When an object is not
located on its natural alignment boundary, accesses to that object are
considered *unaligned*.
Some machines issue a hardware **exception**, or experience **slowdowns** when a
memory access operation occurs from an unaligned address. Examples of such
operations are:
- SIMD instructions on x86. These instructions require all memory accesses to be
on an address that is aligned to 16 bytes.
- On ARM unaligned loads have an extra cycle penalty.
As such, many operations that allocate memory in this package allow to
explicitly specify the alignment of allocated pointers/slices. The default
alignment for all operations is specified in a constant `mem.DEFAULT_ALIGNMENT`.
## Zero by default
Whenever new memory is allocated, via an allocator, or on the stack, by default
Odin will zero-initialize that memory, even if it wasn't explicitly
initialized. This allows for some convenience in certain scenarios and ease of
debugging, which will not be described in detail here.
However zero-initialization can be a cause of slowdowns, when allocating large
buffers. For this reason, allocators have `*_non_zeroed` modes of allocation
that allow the user to request for uninitialized memory and will avoid a
relatively expensive zero-filling of the buffer.
## Naming conventions
The word `size` is used to denote the **size in bytes**. The word `length` is
used to denote the count of objects.
The allocation procedures use the following conventions:
- If the name contains `alloc_bytes` or `resize_bytes`, then the procedure takes
in slice parameters and returns slices.
- If the procedure name contains `alloc` or `resize`, then the procedure takes
in a raw pointer and returns raw pointers.
- If the procedure name contains `free_bytes`, then the procedure takes in a
slice.
- If the procedure name contains `free`, then the procedure takes in a pointer.
Higher-level allocation procedures follow the following naming scheme:
- `new`: Allocates a single object
- `free`: Free a single object (opposite of `new`)
- `make`: Allocate a group of objects
- `delete`: Free a group of objects (opposite of `make`)
*/ */
package mem package mem
+442 -55
View File
@@ -3,49 +3,185 @@ package mem
import "base:runtime" import "base:runtime"
import "base:intrinsics" import "base:intrinsics"
Byte :: runtime.Byte /*
Kilobyte :: runtime.Kilobyte The size, in bytes, of a single byte.
Megabyte :: runtime.Megabyte
Gigabyte :: runtime.Gigabyte
Terabyte :: runtime.Terabyte
Petabyte :: runtime.Petabyte
Exabyte :: runtime.Exabyte
This constant is equal to the value of `1`.
*/
Byte :: runtime.Byte
/*
The size, in bytes, of one kilobyte.
This constant is equal to the amount of bytes in one kilobyte (also known as
kibibyte), which is equal to 1024 bytes.
*/
Kilobyte :: runtime.Kilobyte
/*
The size, in bytes, of one megabyte.
This constant is equal to the amount of bytes in one megabyte (also known as
mebibyte), which is equal to 1024 kilobyte.
*/
Megabyte :: runtime.Megabyte
/*
The size, in bytes, of one gigabyte.
This constant is equal to the amount of bytes in one gigabyte (also known as
gibiibyte), which is equal to 1024 megabytes.
*/
Gigabyte :: runtime.Gigabyte
/*
The size, in bytes, of one terabyte.
This constant is equal to the amount of bytes in one terabyte (also known as
tebiibyte), which is equal to 1024 gigabytes.
*/
Terabyte :: runtime.Terabyte
/*
The size, in bytes, of one petabyte.
This constant is equal to the amount of bytes in one petabyte (also known as
pebiibyte), which is equal to 1024 terabytes.
*/
Petabyte :: runtime.Petabyte
/*
The size, in bytes, of one exabyte.
This constant is equal to the amount of bytes in one exabyte (also known as
exbibyte), which is equal to 1024 petabytes.
*/
Exabyte :: runtime.Exabyte
/*
Set each byte of a memory range to a specific value.
This procedure copies value specified by the `value` parameter into each of the
`len` bytes of a memory range, located at address `data`.
This procedure returns the pointer to `data`.
*/
set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr { set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr {
return runtime.memset(data, i32(value), len) return runtime.memset(data, i32(value), len)
} }
/*
Set each byte of a memory range to zero.
This procedure copies the value `0` into the `len` bytes of a memory range,
starting at address `data`.
This procedure returns the pointer to `data`.
*/
zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
intrinsics.mem_zero(data, len) intrinsics.mem_zero(data, len)
return data return data
} }
/*
Set each byte of a memory range to zero.
This procedure copies the value `0` into the `len` bytes of a memory range,
starting at address `data`.
This procedure returns the pointer to `data`.
Unlike the `zero()` procedure, which can be optimized away or reordered by the
compiler under certain circumstances, `zero_explicit()` procedure can not be
optimized away or reordered with other memory access operations, and the
compiler assumes volatile semantics of the memory.
*/
zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr { zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr {
// This routine tries to avoid the compiler optimizing away the call, // This routine tries to avoid the compiler optimizing away the call,
// so that it is always executed. It is intended to provided // so that it is always executed. It is intended to provide
// equivalent semantics to those provided by the C11 Annex K 3.7.4.1 // equivalent semantics to those provided by the C11 Annex K 3.7.4.1
// memset_s call. // memset_s call.
intrinsics.mem_zero_volatile(data, len) // Use the volatile mem_zero intrinsics.mem_zero_volatile(data, len) // Use the volatile mem_zero
intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering
return data return data
} }
/*
Zero-fill the memory of an object.
This procedure sets each byte of the object pointed to by the pointer `item`
to zero, and returns the pointer to `item`.
*/
zero_item :: proc "contextless" (item: $P/^$T) -> P { zero_item :: proc "contextless" (item: $P/^$T) -> P {
intrinsics.mem_zero(item, size_of(T)) intrinsics.mem_zero(item, size_of(T))
return item return item
} }
/*
Zero-fill the memory of the slice.
This procedure sets each byte of the slice pointed to by the slice `data`
to zero, and returns the slice `data`.
*/
zero_slice :: proc "contextless" (data: $T/[]$E) -> T { zero_slice :: proc "contextless" (data: $T/[]$E) -> T {
zero(raw_data(data), size_of(E)*len(data)) zero(raw_data(data), size_of(E)*len(data))
return data return data
} }
/*
Copy bytes from one memory range to another.
This procedure copies `len` bytes of data, from the memory range pointed to by
the `src` pointer into the memory range pointed to by the `dst` pointer, and
returns the `dst` pointer.
*/
copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr {
intrinsics.mem_copy(dst, src, len) intrinsics.mem_copy(dst, src, len)
return dst return dst
} }
/*
Copy bytes between two non-overlapping memory ranges.
This procedure copies `len` bytes of data, from the memory range pointed to by
the `src` pointer into the memory range pointed to by the `dst` pointer, and
returns the `dst` pointer.
This is a slightly more optimized version of the `copy` procedure that requires
that memory ranges specified by the parameters to this procedure are not
overlapping. If the memory ranges specified by `dst` and `src` pointers overlap,
the behavior of this function may be unpredictable.
*/
copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr {
intrinsics.mem_copy_non_overlapping(dst, src, len) intrinsics.mem_copy_non_overlapping(dst, src, len)
return dst return dst
} }
/*
Compare two memory ranges defined by slices.
This procedure performs a byte-by-byte comparison between memory ranges
specified by slices `a` and `b`, and returns a value, specifying their relative
ordering.
If the return value is:
- Equal to `-1`, then `a` is "smaller" than `b`.
- Equal to `+1`, then `a` is "bigger" than `b`.
- Equal to `0`, then `a` and `b` are equal.
The comparison is performed as follows:
1. Each byte, upto `min(len(a), len(b))` bytes is compared between `a` and `b`.
- If the byte in slice `a` is smaller than a byte in slice `b`, then comparison
stops and this procedure returns `-1`.
- If the byte in slice `a` is bigger than a byte in slice `b`, then comparison
stops and this procedure returns `+1`.
- Otherwise the comparison continues until `min(len(a), len(b))` are compared.
2. If all the bytes in the range are equal, then the lengths of the slices are
compared.
- If the length of slice `a` is smaller than the length of slice `b`, then `-1` is returned.
- If the length of slice `b` is smaller than the length of slice `b`, then `+1` is returned.
- Otherwise `0` is returned.
*/
@(require_results) @(require_results)
compare :: proc "contextless" (a, b: []byte) -> int { compare :: proc "contextless" (a, b: []byte) -> int {
res := compare_byte_ptrs(raw_data(a), raw_data(b), min(len(a), len(b))) res := compare_byte_ptrs(raw_data(a), raw_data(b), min(len(a), len(b)))
@@ -57,16 +193,89 @@ compare :: proc "contextless" (a, b: []byte) -> int {
return res return res
} }
/*
Compare two memory ranges defined by byte pointers.
This procedure performs a byte-by-byte comparison between memory ranges of size
`n` located at addresses `a` and `b`, and returns a value, specifying their relative
ordering.
If the return value is:
- Equal to `-1`, then `a` is "smaller" than `b`.
- Equal to `+1`, then `a` is "bigger" than `b`.
- Equal to `0`, then `a` and `b` are equal.
The comparison is performed as follows:
1. Each byte, upto `n` bytes is compared between `a` and `b`.
- If the byte in `a` is smaller than a byte in `b`, then comparison stops
and this procedure returns `-1`.
- If the byte in `a` is bigger than a byte in `b`, then comparison stops
and this procedure returns `+1`.
- Otherwise the comparison continues until `n` bytes are compared.
2. If all the bytes in the range are equal, this procedure returns `0`.
*/
@(require_results) @(require_results)
compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check { compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check {
return runtime.memory_compare(a, b, n) return runtime.memory_compare(a, b, n)
} }
/*
Compare two memory ranges defined by pointers.
This procedure performs a byte-by-byte comparison between memory ranges of size
`n` located at addresses `a` and `b`, and returns a value, specifying their relative
ordering.
If the return value is:
- Equal to `-1`, then `a` is "smaller" than `b`.
- Equal to `+1`, then `a` is "bigger" than `b`.
- Equal to `0`, then `a` and `b` are equal.
The comparison is performed as follows:
1. Each byte, upto `n` bytes is compared between `a` and `b`.
- If the byte in `a` is smaller than a byte in `b`, then comparison stops
and this procedure returns `-1`.
- If the byte in `a` is bigger than a byte in `b`, then comparison stops
and this procedure returns `+1`.
- Otherwise the comparison continues until `n` bytes are compared.
2. If all the bytes in the range are equal, this procedure returns `0`.
*/
@(require_results)
compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int {
return compare_byte_ptrs((^byte)(a), (^byte)(b), n)
}
/*
Check whether two objects are equal on binary level.
This procedure checks whether the memory ranges occupied by objects `a` and
`b` are equal. See `compare_byte_ptrs()` for how this comparison is done.
*/
@(require_results)
simple_equal :: proc "contextless" (a, b: $T) -> bool where intrinsics.type_is_simple_compare(T) {
a, b := a, b
return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0
}
/*
Check if the memory range defined by a slice is zero-filled.
This procedure checks whether every byte, pointed to by the slice, specified
by the parameter `data`, is zero. If all bytes of the slice are zero, this
procedure returns `true`. Otherwise this procedure returns `false`.
*/
@(require_results) @(require_results)
check_zero :: proc(data: []byte) -> bool { check_zero :: proc(data: []byte) -> bool {
return check_zero_ptr(raw_data(data), len(data)) return check_zero_ptr(raw_data(data), len(data))
} }
/*
Check if the memory range defined defined by a pointer is zero-filled.
This procedure checks whether each of the `len` bytes, starting at address
`ptr` is zero. If all bytes of this range are zero, this procedure returns
`true`. Otherwise this procedure returns `false`.
*/
@(require_results) @(require_results)
check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool {
switch { switch {
@@ -81,57 +290,99 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool {
case 4: return intrinsics.unaligned_load((^u32)(ptr)) == 0 case 4: return intrinsics.unaligned_load((^u32)(ptr)) == 0
case 8: return intrinsics.unaligned_load((^u64)(ptr)) == 0 case 8: return intrinsics.unaligned_load((^u64)(ptr)) == 0
} }
start := uintptr(ptr) start := uintptr(ptr)
start_aligned := align_forward_uintptr(start, align_of(uintptr)) start_aligned := align_forward_uintptr(start, align_of(uintptr))
end := start + uintptr(len) end := start + uintptr(len)
end_aligned := align_backward_uintptr(end, align_of(uintptr)) end_aligned := align_backward_uintptr(end, align_of(uintptr))
for b in start..<start_aligned { for b in start..<start_aligned {
if (^byte)(b)^ != 0 { if (^byte)(b)^ != 0 {
return false return false
} }
} }
for b := start_aligned; b < end_aligned; b += size_of(uintptr) { for b := start_aligned; b < end_aligned; b += size_of(uintptr) {
if (^uintptr)(b)^ != 0 { if (^uintptr)(b)^ != 0 {
return false return false
} }
} }
for b in end_aligned..<end { for b in end_aligned..<end {
if (^byte)(b)^ != 0 { if (^byte)(b)^ != 0 {
return false return false
} }
} }
return true return true
} }
@(require_results) /*
simple_equal :: proc "contextless" (a, b: $T) -> bool where intrinsics.type_is_simple_compare(T) { Offset a given pointer by a given amount.
a, b := a, b
return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0
}
@(require_results) This procedure offsets the pointer `ptr` to an object of type `T`, by the amount
compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int { of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`.
return compare_byte_ptrs((^byte)(a), (^byte)(b), n)
}
**Note**: Prefer to use multipointer types, if possible.
*/
ptr_offset :: intrinsics.ptr_offset ptr_offset :: intrinsics.ptr_offset
/*
Offset a given pointer by a given amount backwards.
This procedure offsets the pointer `ptr` to an object of type `T`, by the amount
of bytes specified by `offset*size_of(T)` in the negative direction, and
returns the pointer `ptr`.
*/
ptr_sub :: intrinsics.ptr_sub ptr_sub :: intrinsics.ptr_sub
/*
Construct a slice from pointer and length.
This procedure creates a slice, that points to `len` amount of objects located
at an address, specified by `ptr`.
*/
@(require_results) @(require_results)
slice_ptr :: proc "contextless" (ptr: ^$T, len: int) -> []T { slice_ptr :: proc "contextless" (ptr: ^$T, len: int) -> []T {
return ([^]T)(ptr)[:len] return ([^]T)(ptr)[:len]
} }
/*
Construct a byte slice from raw pointer and length.
This procedure creates a byte slice, that points to `len` amount of bytes
located at an address specified by `data`.
*/
@(require_results) @(require_results)
byte_slice :: #force_inline proc "contextless" (data: rawptr, #any_int len: int) -> []byte { byte_slice :: #force_inline proc "contextless" (data: rawptr, #any_int len: int) -> []byte {
return ([^]u8)(data)[:max(len, 0)] return ([^]u8)(data)[:max(len, 0)]
} }
/*
Create a byte slice from pointer and length.
This procedure creates a byte slice, pointing to `len` objects, starting from
the address specified by `ptr`.
*/
@(require_results)
ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte {
return transmute([]byte)Raw_Slice{ptr, len*size_of(T)}
}
/*
Obtain the slice, pointing to the contents of `any`.
This procedure returns the slice, pointing to the contents of the specified
value of the `any` type.
*/
@(require_results)
any_to_bytes :: proc "contextless" (val: any) -> []byte {
ti := type_info_of(val.id)
size := ti != nil ? ti.size : 0
return transmute([]byte)Raw_Slice{val.data, size}
}
/*
Obtain a byte slice from any slice.
This procedure returns a slice, that points to the same bytes as the slice,
specified by `slice` and returns the resulting byte slice.
*/
@(require_results) @(require_results)
slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte { slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte {
s := transmute(Raw_Slice)slice s := transmute(Raw_Slice)slice
@@ -139,6 +390,15 @@ slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte {
return transmute([]byte)s return transmute([]byte)s
} }
/*
Transmute slice to a different type.
This procedure performs an operation similar to transmute, returning a slice of
type `T` that points to the same bytes as the slice specified by `slice`
parameter. Unlike plain transmute operation, this procedure adjusts the length
of the resulting slice, such that the resulting slice points to the correct
amount of objects to cover the memory region pointed to by `slice`.
*/
@(require_results) @(require_results)
slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T {
when size_of(A) == 0 || size_of(B) == 0 { when size_of(A) == 0 || size_of(B) == 0 {
@@ -150,12 +410,25 @@ slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T {
} }
} }
/*
Obtain data and length of a slice.
This procedure returns the pointer to the start of the memory region pointed to
by slice `slice` and the length of the slice.
*/
@(require_results) @(require_results)
slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) { slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) {
s := transmute(Raw_Slice)slice s := transmute(Raw_Slice)slice
return (^T)(s.data), s.len return (^T)(s.data), s.len
} }
/*
Create a dynamic array from slice.
This procedure creates a dynamic array, using slice `backing` as the backing
buffer for the dynamic array. The resulting dynamic array can not grow beyond
the size of the specified slice.
*/
@(require_results) @(require_results)
buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E {
return transmute([dynamic]E)Raw_Dynamic_Array{ return transmute([dynamic]E)Raw_Dynamic_Array{
@@ -169,19 +442,12 @@ buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E {
} }
} }
@(require_results) /*
ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte { Check whether a number is a power of two.
return transmute([]byte)Raw_Slice{ptr, len*size_of(T)}
}
@(require_results)
any_to_bytes :: proc "contextless" (val: any) -> []byte {
ti := type_info_of(val.id)
size := ti != nil ? ti.size : 0
return transmute([]byte)Raw_Slice{val.data, size}
}
This procedure checks whether a given pointer-sized unsigned integer contains
a power-of-two value.
*/
@(require_results) @(require_results)
is_power_of_two :: proc "contextless" (x: uintptr) -> bool { is_power_of_two :: proc "contextless" (x: uintptr) -> bool {
if x <= 0 { if x <= 0 {
@@ -190,66 +456,167 @@ is_power_of_two :: proc "contextless" (x: uintptr) -> bool {
return (x & (x-1)) == 0 return (x & (x-1)) == 0
} }
/*
Check if a pointer is aligned.
This procedure checks whether a pointer `x` is aligned to a boundary specified
by `align`, and returns `true` if the pointer is aligned, and false otherwise.
*/
is_aligned :: proc "contextless" (x: rawptr, align: int) -> bool {
p := uintptr(x)
return (p & (1<<uintptr(align) - 1)) == 0
}
/*
Align uintptr forward.
This procedure returns the next address after `ptr`, that is located on the
alignment boundary specified by `align`. If `ptr` is already aligned to `align`
bytes, `ptr` is returned.
The specified alignment must be a power of 2.
*/
@(require_results)
align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr {
assert(is_power_of_two(align))
return (ptr + align-1) & ~(align-1)
}
/*
Align pointer forward.
This procedure returns the next address after `ptr`, that is located on the
alignment boundary specified by `align`. If `ptr` is already aligned to `align`
bytes, `ptr` is returned.
The specified alignment must be a power of 2.
*/
@(require_results) @(require_results)
align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr { align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr {
return rawptr(align_forward_uintptr(uintptr(ptr), align)) return rawptr(align_forward_uintptr(uintptr(ptr), align))
} }
@(require_results) /*
align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr { Align int forward.
assert(is_power_of_two(align))
p := ptr This procedure returns the next address after `ptr`, that is located on the
modulo := p & (align-1) alignment boundary specified by `align`. If `ptr` is already aligned to `align`
if modulo != 0 { bytes, `ptr` is returned.
p += align - modulo
}
return p
}
The specified alignment must be a power of 2.
*/
@(require_results) @(require_results)
align_forward_int :: proc(ptr, align: int) -> int { align_forward_int :: proc(ptr, align: int) -> int {
return int(align_forward_uintptr(uintptr(ptr), uintptr(align))) return int(align_forward_uintptr(uintptr(ptr), uintptr(align)))
} }
/*
Align uint forward.
This procedure returns the next address after `ptr`, that is located on the
alignment boundary specified by `align`. If `ptr` is already aligned to `align`
bytes, `ptr` is returned.
The specified alignment must be a power of 2.
*/
@(require_results) @(require_results)
align_forward_uint :: proc(ptr, align: uint) -> uint { align_forward_uint :: proc(ptr, align: uint) -> uint {
return uint(align_forward_uintptr(uintptr(ptr), uintptr(align))) return uint(align_forward_uintptr(uintptr(ptr), uintptr(align)))
} }
/*
Align uintptr backwards.
This procedure returns the previous address before `ptr`, that is located on the
alignment boundary specified by `align`. If `ptr` is already aligned to `align`
bytes, `ptr` is returned.
The specified alignment must be a power of 2.
*/
@(require_results)
align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr {
assert(is_power_of_two(align))
return ptr & ~(align-1)
}
/*
Align rawptr backwards.
This procedure returns the previous address before `ptr`, that is located on the
alignment boundary specified by `align`. If `ptr` is already aligned to `align`
bytes, `ptr` is returned.
The specified alignment must be a power of 2.
*/
@(require_results) @(require_results)
align_backward :: proc(ptr: rawptr, align: uintptr) -> rawptr { align_backward :: proc(ptr: rawptr, align: uintptr) -> rawptr {
return rawptr(align_backward_uintptr(uintptr(ptr), align)) return rawptr(align_backward_uintptr(uintptr(ptr), align))
} }
@(require_results) /*
align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { Align int backwards.
return align_forward_uintptr(ptr - align + 1, align)
}
This procedure returns the previous address before `ptr`, that is located on the
alignment boundary specified by `align`. If `ptr` is already aligned to `align`
bytes, `ptr` is returned.
The specified alignment must be a power of 2.
*/
@(require_results) @(require_results)
align_backward_int :: proc(ptr, align: int) -> int { align_backward_int :: proc(ptr, align: int) -> int {
return int(align_backward_uintptr(uintptr(ptr), uintptr(align))) return int(align_backward_uintptr(uintptr(ptr), uintptr(align)))
} }
/*
Align uint backwards.
This procedure returns the previous address before `ptr`, that is located on the
alignment boundary specified by `align`. If `ptr` is already aligned to `align`
bytes, `ptr` is returned.
The specified alignment must be a power of 2.
*/
@(require_results) @(require_results)
align_backward_uint :: proc(ptr, align: uint) -> uint { align_backward_uint :: proc(ptr, align: uint) -> uint {
return uint(align_backward_uintptr(uintptr(ptr), uintptr(align))) return uint(align_backward_uintptr(uintptr(ptr), uintptr(align)))
} }
/*
Create a context with a given allocator.
This procedure returns a copy of the current context with the allocator replaced
by the allocator `a`.
*/
@(require_results) @(require_results)
context_from_allocator :: proc(a: Allocator) -> type_of(context) { context_from_allocator :: proc(a: Allocator) -> type_of(context) {
context.allocator = a context.allocator = a
return context return context
} }
/*
Copy the value from a pointer into a value.
This procedure copies the object of type `T` pointed to by the pointer `ptr`
into a new stack-allocated value and returns that value.
*/
@(require_results) @(require_results)
reinterpret_copy :: proc "contextless" ($T: typeid, ptr: rawptr) -> (value: T) { reinterpret_copy :: proc "contextless" ($T: typeid, ptr: rawptr) -> (value: T) {
copy(&value, ptr, size_of(T)) copy(&value, ptr, size_of(T))
return return
} }
/*
Dynamic array with a fixed capacity buffer.
This type represents dynamic arrays with a fixed-size backing buffer. Upon
allocating memory beyond reaching the maximum capacity, allocations from fixed
byte buffers return `nil` and no error.
*/
Fixed_Byte_Buffer :: distinct [dynamic]byte Fixed_Byte_Buffer :: distinct [dynamic]byte
/*
Create a fixed byte buffer from a slice.
*/
@(require_results) @(require_results)
make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buffer { make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buffer {
s := transmute(Raw_Slice)backing s := transmute(Raw_Slice)backing
@@ -264,40 +631,60 @@ make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buf
return transmute(Fixed_Byte_Buffer)d return transmute(Fixed_Byte_Buffer)d
} }
/*
General-purpose align formula.
This procedure is equivalent to `align_forward`, but it does not require the
alignment to be a power of two.
*/
@(require_results) @(require_results)
align_formula :: proc "contextless" (size, align: int) -> int { align_formula :: proc "contextless" (size, align: int) -> int {
result := size + align-1 result := size + align-1
return result - result%align return result - result%align
} }
/*
Calculate the padding for header preceding aligned data.
This procedure returns the padding, following the specified pointer `ptr` that
will be able to fit in a header of the size `header_size`, immediately
preceding the memory region, aligned on a boundary specified by `align`. See
the following diagram for a visual representation.
header size
|<------>|
+---+--------+------------- - - -
| HEADER | DATA...
+---+--------+------------- - - -
^ ^
|<---------->|
| padding |
ptr aligned ptr
The function takes in `ptr` and `header_size`, as well as the required
alignment for `DATA`. The return value of the function is the padding between
`ptr` and `aligned_ptr` that will be able to fit the header.
*/
@(require_results) @(require_results)
calc_padding_with_header :: proc "contextless" (ptr: uintptr, align: uintptr, header_size: int) -> int { calc_padding_with_header :: proc "contextless" (ptr: uintptr, align: uintptr, header_size: int) -> int {
p, a := ptr, align p, a := ptr, align
modulo := p & (a-1) modulo := p & (a-1)
padding := uintptr(0) padding := uintptr(0)
if modulo != 0 { if modulo != 0 {
padding = a - modulo padding = a - modulo
} }
needed_space := uintptr(header_size) needed_space := uintptr(header_size)
if padding < needed_space { if padding < needed_space {
needed_space -= padding needed_space -= padding
if needed_space & (a-1) > 0 { if needed_space & (a-1) > 0 {
padding += align * (1+(needed_space/align)) padding += align * (1+(needed_space/align))
} else { } else {
padding += align * (needed_space/align) padding += align * (needed_space/align)
} }
} }
return int(padding) return int(padding)
} }
@(require_results, deprecated="prefer 'slice.clone'") @(require_results, deprecated="prefer 'slice.clone'")
clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) { clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) {
new_slice, _ = make(T, len(slice), allocator, loc) new_slice, _ = make(T, len(slice), allocator, loc)
+23 -4
View File
@@ -3,17 +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{
@@ -22,11 +36,16 @@ mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator {
} }
} }
mutex_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, mutex_allocator_proc :: proc(
size, alignment: int, allocator_data: rawptr,
old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { mode: Allocator_Mode,
size: int,
alignment: int,
old_memory: rawptr,
old_size: int,
loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) {
m := (^Mutex_Allocator)(allocator_data) m := (^Mutex_Allocator)(allocator_data)
sync.mutex_guard(&m.mutex) sync.mutex_guard(&m.mutex)
return m.backing.procedure(m.backing.data, mode, size, alignment, old_memory, old_size, loc) return m.backing.procedure(m.backing.data, mode, size, alignment, old_memory, old_size, loc)
} }
+86 -12
View File
@@ -3,26 +3,100 @@ package mem
import "base:builtin" import "base:builtin"
import "base:runtime" import "base:runtime"
Raw_Any :: runtime.Raw_Any /*
Raw_String :: runtime.Raw_String Memory layout of the `any` type.
Raw_Cstring :: runtime.Raw_Cstring */
Raw_Slice :: runtime.Raw_Slice Raw_Any :: runtime.Raw_Any
Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
Raw_Map :: runtime.Raw_Map
Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer
Raw_Complex32 :: runtime.Raw_Complex32 /*
Raw_Complex64 :: runtime.Raw_Complex64 Memory layout of the `string` type.
Raw_Complex128 :: runtime.Raw_Complex128 */
Raw_Quaternion64 :: runtime.Raw_Quaternion64 Raw_String :: runtime.Raw_String
/*
Memory layout of the `cstring` type.
*/
Raw_Cstring :: runtime.Raw_Cstring
/*
Memory layout of `[]T` types.
*/
Raw_Slice :: runtime.Raw_Slice
/*
Memory layout of `[dynamic]T` types.
*/
Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
/*
Memory layout of `map[K]V` types.
*/
Raw_Map :: runtime.Raw_Map
/*
Memory layout of `#soa []T` types.
*/
Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer
/*
Memory layout of the `complex32` type.
*/
Raw_Complex32 :: runtime.Raw_Complex32
/*
Memory layout of the `complex64` type.
*/
Raw_Complex64 :: runtime.Raw_Complex64
/*
Memory layout of the `complex128` type.
*/
Raw_Complex128 :: runtime.Raw_Complex128
/*
Memory layout of the `quaternion64` type.
*/
Raw_Quaternion64 :: runtime.Raw_Quaternion64
/*
Memory layout of the `quaternion128` type.
*/
Raw_Quaternion128 :: runtime.Raw_Quaternion128 Raw_Quaternion128 :: runtime.Raw_Quaternion128
/*
Memory layout of the `quaternion256` type.
*/
Raw_Quaternion256 :: runtime.Raw_Quaternion256 Raw_Quaternion256 :: runtime.Raw_Quaternion256
Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar
/*
Memory layout of the `quaternion64` type.
*/
Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar
/*
Memory layout of the `quaternion128` type.
*/
Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar
/*
Memory layout of the `quaternion256` type.
*/
Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar
/*
Create a value of the any type.
This procedure creates a value with type `any` that points to an object with
typeid `id` located at an address specified by `data`.
*/
make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { make_any :: proc "contextless" (data: rawptr, id: typeid) -> any {
return transmute(any)Raw_Any{data, id} return transmute(any)Raw_Any{data, id}
} }
/*
Obtain pointer to the data.
This procedure returns the pointer to the data of a slice, string, or a dynamic
array.
*/
raw_data :: builtin.raw_data raw_data :: builtin.raw_data
+245 -106
View File
@@ -1,52 +1,36 @@
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
// 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 Rollback stack max head block size.
// valid, so long as the block allocator can handle it.
// This limitation is due to the size of `prev_ptr`, but it is only for the
// This is because allocations over the block size are not split up if the item head block; any allocation in excess of the allocator's `block_size` is
// within is freed; they are immediately returned to the block allocator. valid, so long as the block allocator can handle it.
This is because allocations over the block size are not split up if the item
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,
@@ -54,13 +38,15 @@ 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,
block_allocator: Allocator, block_allocator: Allocator,
} }
@(private="file", require_results) @(private="file", require_results)
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
start := raw_data(block.buffer) start := raw_data(block.buffer)
@@ -110,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
@@ -128,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; /**/ {
@@ -141,45 +133,75 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.offset = 0 stack.head.offset = 0
} }
@(private="file", require_results) /*
rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { Allocate memory using the rollback stack allocator.
if ptr != nil { */
if block, _, ok := rb_find_last_alloc(stack, ptr); ok { @(require_results)
// `block.offset` should never underflow because it is contingent rb_alloc :: proc(
// on `old_size` in the first place, assuming sane arguments. stack: ^Rollback_Stack,
assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") size: int,
alignment := DEFAULT_ALIGNMENT,
if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { loc := #caller_location,
// Prevent singleton allocations from fragmenting by forbidding ) -> (rawptr, Allocator_Error) {
// them to shrink, removing the possibility of overflow bugs. bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc)
if len(block.buffer) <= stack.block_size { if bytes != nil {
block.offset += cast(uintptr)size - cast(uintptr)old_size zero_slice(bytes)
}
#no_bounds_check return (cast([^]byte)ptr)[:size], nil
}
}
} }
return raw_data(bytes), err
result = rb_alloc(stack, size, alignment) or_return
runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size)
err = rb_free(stack, ptr)
return
} }
@(private="file", require_results) /*
rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { Allocate memory using the rollback stack allocator.
*/
@(require_results)
rb_alloc_bytes :: proc(
stack: ^Rollback_Stack,
size: int,
alignment := DEFAULT_ALIGNMENT,
loc := #caller_location,
) -> ([]byte, Allocator_Error) {
bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc)
if bytes != nil {
zero_slice(bytes)
}
return bytes, err
}
/*
Allocate non-initialized memory using the rollback stack allocator.
*/
@(require_results)
rb_alloc_non_zeroed :: proc(
stack: ^Rollback_Stack,
size: int,
alignment := DEFAULT_ALIGNMENT,
loc := #caller_location,
) -> (rawptr, Allocator_Error) {
bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc)
return raw_data(bytes), err
}
/*
Allocate non-initialized memory using the rollback stack allocator.
*/
@(require_results)
rb_alloc_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack,
size: int,
alignment := DEFAULT_ALIGNMENT,
loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) {
assert(size >= 0, "Size must be positive or zero.", loc)
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
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
@@ -188,10 +210,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 {
@@ -201,54 +221,150 @@ 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
} }
/*
Resize an allocation owned by rollback stack allocator.
*/
@(require_results)
rb_resize :: proc(
stack: ^Rollback_Stack,
old_ptr: rawptr,
old_size: int,
size: int,
alignment := DEFAULT_ALIGNMENT,
loc := #caller_location,
) -> (rawptr, Allocator_Error) {
bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc)
if bytes != nil {
if old_ptr == nil {
zero_slice(bytes)
} else if size > old_size {
zero_slice(bytes[old_size:])
}
}
return raw_data(bytes), err
}
/*
Resize an allocation owned by rollback stack allocator.
*/
@(require_results)
rb_resize_bytes :: proc(
stack: ^Rollback_Stack,
old_memory: []byte,
size: int,
alignment := DEFAULT_ALIGNMENT,
loc := #caller_location,
) -> ([]u8, Allocator_Error) {
bytes, err := rb_resize_bytes_non_zeroed(stack, old_memory, size, alignment, loc)
if bytes != nil {
if old_memory == nil {
zero_slice(bytes)
} else if size > len(old_memory) {
zero_slice(bytes[len(old_memory):])
}
}
return bytes, err
}
/*
Resize an allocation owned by rollback stack allocator without explicit
zero-initialization.
*/
@(require_results)
rb_resize_non_zeroed :: proc(
stack: ^Rollback_Stack,
old_ptr: rawptr,
old_size: int,
size: int,
alignment := DEFAULT_ALIGNMENT,
loc := #caller_location,
) -> (rawptr, Allocator_Error) {
bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc)
return raw_data(bytes), err
}
/*
Resize an allocation owned by rollback stack allocator without explicit
zero-initialization.
*/
@(require_results)
rb_resize_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack,
old_memory: []byte,
size: int,
alignment := DEFAULT_ALIGNMENT,
loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) {
old_size := len(old_memory)
ptr := raw_data(old_memory)
assert(size >= 0, "Size must be positive or zero.", loc)
assert(old_size >= 0, "Old size must be positive or zero.", loc)
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
if ptr != nil {
if block, _, ok := rb_find_last_alloc(stack, ptr); ok {
// `block.offset` should never underflow because it is contingent
// on `old_size` in the first place, assuming sane arguments.
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) {
// Prevent singleton allocations from fragmenting by forbidding
// them to shrink, removing the possibility of overflow bugs.
if len(block.buffer) <= stack.block_size {
block.offset += cast(uintptr)size - cast(uintptr)old_size
}
#no_bounds_check return (ptr)[:size], nil
}
}
}
result = rb_alloc_bytes_non_zeroed(stack, size, alignment) or_return
runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size)
err = rb_free(stack, ptr)
return
}
@(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,
@@ -261,22 +377,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)
@@ -285,6 +404,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 {
@@ -294,48 +444,37 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
} }
@(require_results) @(require_results)
rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, rollback_stack_allocator_proc :: proc(
size, alignment: int, allocator_data: rawptr,
old_memory: rawptr, old_size: int, location := #caller_location, mode: Allocator_Mode,
size, alignment: int,
old_memory: rawptr,
old_size: int,
loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) { ) -> (result: []byte, err: Allocator_Error) {
stack := cast(^Rollback_Stack)allocator_data stack := cast(^Rollback_Stack)allocator_data
switch mode { switch mode {
case .Alloc, .Alloc_Non_Zeroed: case .Alloc:
assert(size >= 0, "Size must be positive or zero.", location) return rb_alloc_bytes(stack, size, alignment, loc)
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) case .Alloc_Non_Zeroed:
result = rb_alloc(stack, size, alignment) or_return return rb_alloc_bytes_non_zeroed(stack, size, alignment, loc)
if mode == .Alloc {
zero_slice(result)
}
case .Free: case .Free:
err = rb_free(stack, old_memory) return nil, rb_free(stack, old_memory)
case .Free_All: case .Free_All:
rb_free_all(stack) rb_free_all(stack)
return nil, nil
case .Resize, .Resize_Non_Zeroed: case .Resize:
assert(size >= 0, "Size must be positive or zero.", location) return rb_resize_bytes(stack, byte_slice(old_memory, old_size), size, alignment, loc)
assert(old_size >= 0, "Old size must be positive or zero.", location) case .Resize_Non_Zeroed:
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) return rb_resize_bytes_non_zeroed(stack, byte_slice(old_memory, old_size), size, alignment, loc)
result = rb_resize(stack, old_memory, old_size, size, alignment) or_return
#no_bounds_check if mode == .Resize && size > 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
} }
+99 -22
View File
@@ -4,50 +4,85 @@ 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,
} }
Tracking_Allocator :: struct {
backing: Allocator,
allocation_map: map[rawptr]Tracking_Allocator_Entry,
bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
mutex: sync.Mutex,
clear_on_free_all: bool,
total_memory_allocated: i64, /*
total_allocation_count: i64, Tracking allocator data.
total_memory_freed: i64, */
total_free_count: i64, Tracking_Allocator :: struct {
peak_memory_allocated: i64, backing: Allocator,
allocation_map: map[rawptr]Tracking_Allocator_Entry,
bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
mutex: sync.Mutex,
clear_on_free_all: bool,
total_memory_allocated: i64,
total_allocation_count: i64,
total_memory_freed: i64,
total_free_count: i64,
peak_memory_allocated: i64,
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
t.bad_free_array.allocator = internals_allocator t.bad_free_array.allocator = internals_allocator
if .Free_All in query_features(t.backing) { if .Free_All in query_features(t.backing) {
t.clear_on_free_all = true t.clear_on_free_all = true
} }
} }
/*
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 the tracking allocator.
// Clear only the current allocation data while keeping the totals intact. 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)
@@ -56,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)
@@ -70,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{
@@ -78,9 +150,14 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
} }
} }
tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, tracking_allocator_proc :: proc(
size, alignment: int, allocator_data: rawptr,
old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { mode: Allocator_Mode,
size, alignment: int,
old_memory: rawptr,
old_size: int,
loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) {
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_allocated += i64(entry.size) data.total_memory_allocated += i64(entry.size)
data.total_allocation_count += 1 data.total_allocation_count += 1
+2 -2
View File
@@ -6,7 +6,7 @@ import "core:mem"
expect_pool_allocation :: proc(t: ^testing.T, expected_used_bytes, num_bytes, alignment: int) { expect_pool_allocation :: proc(t: ^testing.T, expected_used_bytes, num_bytes, alignment: int) {
pool: mem.Dynamic_Pool pool: mem.Dynamic_Pool
mem.dynamic_pool_init(pool = &pool, alignment = alignment) mem.dynamic_pool_init(&pool, alignment = alignment)
pool_allocator := mem.dynamic_pool_allocator(&pool) pool_allocator := mem.dynamic_pool_allocator(&pool)
element, err := mem.alloc(num_bytes, alignment, pool_allocator) element, err := mem.alloc(num_bytes, alignment, pool_allocator)
@@ -48,7 +48,7 @@ expect_pool_allocation_out_of_band :: proc(t: ^testing.T, num_bytes, out_band_si
testing.expect(t, num_bytes >= out_band_size, "Sanity check failed, your test call is flawed! Make sure that num_bytes >= out_band_size!") testing.expect(t, num_bytes >= out_band_size, "Sanity check failed, your test call is flawed! Make sure that num_bytes >= out_band_size!")
pool: mem.Dynamic_Pool pool: mem.Dynamic_Pool
mem.dynamic_pool_init(pool = &pool, out_band_size = out_band_size) mem.dynamic_pool_init(&pool, out_band_size = out_band_size)
pool_allocator := mem.dynamic_pool_allocator(&pool) pool_allocator := mem.dynamic_pool_allocator(&pool)
element, err := mem.alloc(num_bytes, allocator = pool_allocator) element, err := mem.alloc(num_bytes, allocator = pool_allocator)