mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-13 01:21:38 -07:00
Merge pull request #4209 from flysand7/core-mem
[core/mem]: Document, refactor, reformat!
This commit is contained in:
@@ -118,16 +118,15 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r
|
||||
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) {
|
||||
if size == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if allocator.procedure == nil {
|
||||
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
|
||||
if size == 0 || allocator.procedure == nil{
|
||||
return nil, nil
|
||||
}
|
||||
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) {
|
||||
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
|
||||
if size == 0 || allocator.procedure == 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) {
|
||||
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
|
||||
if size == 0 || allocator.procedure == 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) {
|
||||
assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
|
||||
if allocator.procedure == 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) {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
+935
-79
File diff suppressed because it is too large
Load Diff
+1989
-849
File diff suppressed because it is too large
Load Diff
+103
-23
@@ -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
|
||||
in your program and report leaks and bad frees:
|
||||
## Pointers, multipointers, and slices
|
||||
|
||||
Example:
|
||||
package foo
|
||||
A *pointer* is an abstraction of an *address*, a numberic value representing the
|
||||
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"
|
||||
import "core:fmt"
|
||||
A multipointer is a pointer that points to multiple objects. Unlike a pointer,
|
||||
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() {
|
||||
// do stuff
|
||||
}
|
||||
When object's values are read through a pointer, that operation is called a
|
||||
*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() {
|
||||
track: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track, context.allocator)
|
||||
defer mem.tracking_allocator_destroy(&track)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
## Allocators
|
||||
|
||||
_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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Using different kinds of *allocators* for different purposes can solve these
|
||||
problems. The allocators are typically optimized for specific use-cases and
|
||||
can potentially simplify the memory management code.
|
||||
|
||||
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
|
||||
|
||||
+442
-55
@@ -3,49 +3,185 @@ package mem
|
||||
import "base:runtime"
|
||||
import "base:intrinsics"
|
||||
|
||||
Byte :: runtime.Byte
|
||||
Kilobyte :: runtime.Kilobyte
|
||||
Megabyte :: runtime.Megabyte
|
||||
Gigabyte :: runtime.Gigabyte
|
||||
Terabyte :: runtime.Terabyte
|
||||
Petabyte :: runtime.Petabyte
|
||||
Exabyte :: runtime.Exabyte
|
||||
/*
|
||||
The size, in bytes, of a single byte.
|
||||
|
||||
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 {
|
||||
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 {
|
||||
intrinsics.mem_zero(data, len)
|
||||
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 {
|
||||
// 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
|
||||
// memset_s call.
|
||||
intrinsics.mem_zero_volatile(data, len) // Use the volatile mem_zero
|
||||
intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering
|
||||
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 {
|
||||
intrinsics.mem_zero(item, size_of(T))
|
||||
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(raw_data(data), size_of(E)*len(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 {
|
||||
intrinsics.mem_copy(dst, src, len)
|
||||
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 {
|
||||
intrinsics.mem_copy_non_overlapping(dst, src, len)
|
||||
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)
|
||||
compare :: proc "contextless" (a, b: []byte) -> int {
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
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)
|
||||
compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check {
|
||||
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)
|
||||
check_zero :: proc(data: []byte) -> bool {
|
||||
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)
|
||||
check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool {
|
||||
switch {
|
||||
@@ -81,57 +290,99 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool {
|
||||
case 4: return intrinsics.unaligned_load((^u32)(ptr)) == 0
|
||||
case 8: return intrinsics.unaligned_load((^u64)(ptr)) == 0
|
||||
}
|
||||
|
||||
start := uintptr(ptr)
|
||||
start_aligned := align_forward_uintptr(start, align_of(uintptr))
|
||||
end := start + uintptr(len)
|
||||
end_aligned := align_backward_uintptr(end, align_of(uintptr))
|
||||
|
||||
for b in start..<start_aligned {
|
||||
if (^byte)(b)^ != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for b := start_aligned; b < end_aligned; b += size_of(uintptr) {
|
||||
if (^uintptr)(b)^ != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for b in end_aligned..<end {
|
||||
if (^byte)(b)^ != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(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
|
||||
}
|
||||
/*
|
||||
Offset a given pointer by a given amount.
|
||||
|
||||
@(require_results)
|
||||
compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int {
|
||||
return compare_byte_ptrs((^byte)(a), (^byte)(b), n)
|
||||
}
|
||||
This procedure offsets the pointer `ptr` to an object of type `T`, by the amount
|
||||
of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`.
|
||||
|
||||
**Note**: Prefer to use multipointer types, if possible.
|
||||
*/
|
||||
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
|
||||
|
||||
/*
|
||||
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)
|
||||
slice_ptr :: proc "contextless" (ptr: ^$T, len: int) -> []T {
|
||||
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)
|
||||
byte_slice :: #force_inline proc "contextless" (data: rawptr, #any_int len: int) -> []byte {
|
||||
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)
|
||||
slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte {
|
||||
s := transmute(Raw_Slice)slice
|
||||
@@ -139,6 +390,15 @@ slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte {
|
||||
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)
|
||||
slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T {
|
||||
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)
|
||||
slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) {
|
||||
s := transmute(Raw_Slice)slice
|
||||
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)
|
||||
buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E {
|
||||
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 {
|
||||
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}
|
||||
}
|
||||
|
||||
/*
|
||||
Check whether a number is a power of two.
|
||||
|
||||
This procedure checks whether a given pointer-sized unsigned integer contains
|
||||
a power-of-two value.
|
||||
*/
|
||||
@(require_results)
|
||||
is_power_of_two :: proc "contextless" (x: uintptr) -> bool {
|
||||
if x <= 0 {
|
||||
@@ -190,66 +456,167 @@ is_power_of_two :: proc "contextless" (x: uintptr) -> bool {
|
||||
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)
|
||||
align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr {
|
||||
return rawptr(align_forward_uintptr(uintptr(ptr), align))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr {
|
||||
assert(is_power_of_two(align))
|
||||
/*
|
||||
Align int forward.
|
||||
|
||||
p := ptr
|
||||
modulo := p & (align-1)
|
||||
if modulo != 0 {
|
||||
p += align - modulo
|
||||
}
|
||||
return p
|
||||
}
|
||||
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_int :: proc(ptr, align: int) -> int {
|
||||
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)
|
||||
align_forward_uint :: proc(ptr, align: uint) -> uint {
|
||||
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)
|
||||
align_backward :: proc(ptr: rawptr, align: uintptr) -> rawptr {
|
||||
return rawptr(align_backward_uintptr(uintptr(ptr), align))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr {
|
||||
return align_forward_uintptr(ptr - align + 1, align)
|
||||
}
|
||||
/*
|
||||
Align int 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_int :: proc(ptr, align: int) -> int {
|
||||
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)
|
||||
align_backward_uint :: proc(ptr, align: uint) -> uint {
|
||||
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)
|
||||
context_from_allocator :: proc(a: Allocator) -> type_of(context) {
|
||||
context.allocator = a
|
||||
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)
|
||||
reinterpret_copy :: proc "contextless" ($T: typeid, ptr: rawptr) -> (value: T) {
|
||||
copy(&value, ptr, size_of(T))
|
||||
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
|
||||
|
||||
/*
|
||||
Create a fixed byte buffer from a slice.
|
||||
*/
|
||||
@(require_results)
|
||||
make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buffer {
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
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)
|
||||
align_formula :: proc "contextless" (size, align: int) -> int {
|
||||
result := size + align-1
|
||||
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)
|
||||
calc_padding_with_header :: proc "contextless" (ptr: uintptr, align: uintptr, header_size: int) -> int {
|
||||
p, a := ptr, align
|
||||
modulo := p & (a-1)
|
||||
|
||||
padding := uintptr(0)
|
||||
if modulo != 0 {
|
||||
padding = a - modulo
|
||||
}
|
||||
|
||||
needed_space := uintptr(header_size)
|
||||
if padding < needed_space {
|
||||
needed_space -= padding
|
||||
|
||||
if needed_space & (a-1) > 0 {
|
||||
padding += align * (1+(needed_space/align))
|
||||
} else {
|
||||
padding += align * (needed_space/align)
|
||||
}
|
||||
}
|
||||
|
||||
return int(padding)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results, deprecated="prefer 'slice.clone'")
|
||||
clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) {
|
||||
new_slice, _ = make(T, len(slice), allocator, loc)
|
||||
|
||||
@@ -3,17 +3,31 @@ package mem
|
||||
|
||||
import "core:sync"
|
||||
|
||||
/*
|
||||
The data for mutex allocator.
|
||||
*/
|
||||
Mutex_Allocator :: struct {
|
||||
backing: Allocator,
|
||||
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) {
|
||||
m.backing = backing_allocator
|
||||
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)
|
||||
mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator {
|
||||
return Allocator{
|
||||
@@ -22,11 +36,16 @@ mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator {
|
||||
}
|
||||
}
|
||||
|
||||
mutex_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) {
|
||||
mutex_allocator_proc :: proc(
|
||||
allocator_data: rawptr,
|
||||
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)
|
||||
|
||||
sync.mutex_guard(&m.mutex)
|
||||
return m.backing.procedure(m.backing.data, mode, size, alignment, old_memory, old_size, loc)
|
||||
}
|
||||
|
||||
+86
-12
@@ -3,26 +3,100 @@ package mem
|
||||
import "base:builtin"
|
||||
import "base:runtime"
|
||||
|
||||
Raw_Any :: runtime.Raw_Any
|
||||
Raw_String :: runtime.Raw_String
|
||||
Raw_Cstring :: runtime.Raw_Cstring
|
||||
Raw_Slice :: runtime.Raw_Slice
|
||||
Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
|
||||
Raw_Map :: runtime.Raw_Map
|
||||
Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer
|
||||
/*
|
||||
Memory layout of the `any` type.
|
||||
*/
|
||||
Raw_Any :: runtime.Raw_Any
|
||||
|
||||
Raw_Complex32 :: runtime.Raw_Complex32
|
||||
Raw_Complex64 :: runtime.Raw_Complex64
|
||||
Raw_Complex128 :: runtime.Raw_Complex128
|
||||
Raw_Quaternion64 :: runtime.Raw_Quaternion64
|
||||
/*
|
||||
Memory layout of the `string` type.
|
||||
*/
|
||||
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
|
||||
|
||||
/*
|
||||
Memory layout of the `quaternion256` type.
|
||||
*/
|
||||
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
|
||||
|
||||
/*
|
||||
Memory layout of the `quaternion256` type.
|
||||
*/
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -1,52 +1,36 @@
|
||||
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"
|
||||
|
||||
/*
|
||||
Rollback stack default block size.
|
||||
*/
|
||||
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
|
||||
// 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.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
/*
|
||||
Allocation header of the rollback stack allocator.
|
||||
*/
|
||||
Rollback_Stack_Header :: bit_field u64 {
|
||||
prev_offset: uintptr | 32,
|
||||
is_free: bool | 1,
|
||||
prev_ptr: uintptr | 31,
|
||||
}
|
||||
|
||||
/*
|
||||
Block header of the rollback stack allocator.
|
||||
*/
|
||||
Rollback_Stack_Block :: struct {
|
||||
next_block: ^Rollback_Stack_Block,
|
||||
last_alloc: rawptr,
|
||||
@@ -54,13 +38,15 @@ Rollback_Stack_Block :: struct {
|
||||
buffer: []byte,
|
||||
}
|
||||
|
||||
/*
|
||||
Rollback stack allocator data.
|
||||
*/
|
||||
Rollback_Stack :: struct {
|
||||
head: ^Rollback_Stack_Block,
|
||||
block_size: int,
|
||||
block_allocator: Allocator,
|
||||
}
|
||||
|
||||
|
||||
@(private="file", require_results)
|
||||
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
|
||||
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)
|
||||
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
Free all memory owned by the rollback stack allocator.
|
||||
*/
|
||||
@(private="file")
|
||||
rb_free_all :: proc(stack: ^Rollback_Stack) {
|
||||
for block := stack.head.next_block; block != nil; /**/ {
|
||||
@@ -141,45 +133,75 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
|
||||
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) {
|
||||
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 (cast([^]byte)ptr)[:size], nil
|
||||
}
|
||||
}
|
||||
/*
|
||||
Allocate memory using the rollback stack allocator.
|
||||
*/
|
||||
@(require_results)
|
||||
rb_alloc :: 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)
|
||||
if bytes != nil {
|
||||
zero_slice(bytes)
|
||||
}
|
||||
|
||||
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
|
||||
return raw_data(bytes), err
|
||||
}
|
||||
|
||||
@(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
|
||||
for block := stack.head; /**/; block = block.next_block {
|
||||
when !ODIN_DISABLE_ASSERT {
|
||||
allocated_new_block: bool
|
||||
}
|
||||
|
||||
if block == nil {
|
||||
if stack.block_allocator.procedure == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1
|
||||
new_block_size := max(minimum_size_required, stack.block_size)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) {
|
||||
when !ODIN_DISABLE_ASSERT {
|
||||
if allocated_new_block {
|
||||
@@ -201,54 +221,150 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt
|
||||
parent = block
|
||||
continue
|
||||
}
|
||||
|
||||
header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):])
|
||||
ptr := start[padding:]
|
||||
|
||||
header^ = {
|
||||
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),
|
||||
is_free = false,
|
||||
}
|
||||
|
||||
block.last_alloc = ptr
|
||||
block.offset += padding + cast(uintptr)size
|
||||
|
||||
if len(block.buffer) > stack.block_size {
|
||||
// This block exceeds the allocator's standard block size and is considered a singleton.
|
||||
// Prevent any further allocations on it.
|
||||
block.offset = cast(uintptr)len(block.buffer)
|
||||
}
|
||||
|
||||
#no_bounds_check return ptr[:size], nil
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
block = cast(^Rollback_Stack_Block)raw_data(buffer)
|
||||
#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Initialize the rollback stack allocator using a fixed backing buffer.
|
||||
*/
|
||||
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)
|
||||
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^ = {}
|
||||
#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
|
||||
|
||||
stack^ = {}
|
||||
stack.head = block
|
||||
stack.block_size = len(block.buffer)
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the rollback stack alocator using a backing block allocator.
|
||||
*/
|
||||
rollback_stack_init_dynamic :: proc(
|
||||
stack: ^Rollback_Stack,
|
||||
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.
|
||||
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
|
||||
|
||||
stack^ = {}
|
||||
stack.head = block
|
||||
stack.block_size = block_size
|
||||
stack.block_allocator = block_allocator
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the rollback stack.
|
||||
*/
|
||||
rollback_stack_init :: proc {
|
||||
rollback_stack_init_buffered,
|
||||
rollback_stack_init_dynamic,
|
||||
}
|
||||
|
||||
/*
|
||||
Destroy a rollback stack.
|
||||
*/
|
||||
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
|
||||
if stack.block_allocator.procedure != nil {
|
||||
rb_free_all(stack)
|
||||
@@ -285,6 +404,37 @@ rollback_stack_destroy :: proc(stack: ^Rollback_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)
|
||||
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
|
||||
return Allocator {
|
||||
@@ -294,48 +444,37 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, location := #caller_location,
|
||||
rollback_stack_allocator_proc :: proc(
|
||||
allocator_data: rawptr,
|
||||
mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr,
|
||||
old_size: int,
|
||||
loc := #caller_location,
|
||||
) -> (result: []byte, err: Allocator_Error) {
|
||||
stack := cast(^Rollback_Stack)allocator_data
|
||||
|
||||
switch mode {
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
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)
|
||||
result = rb_alloc(stack, size, alignment) or_return
|
||||
|
||||
if mode == .Alloc {
|
||||
zero_slice(result)
|
||||
}
|
||||
|
||||
case .Alloc:
|
||||
return rb_alloc_bytes(stack, size, alignment, loc)
|
||||
case .Alloc_Non_Zeroed:
|
||||
return rb_alloc_bytes_non_zeroed(stack, size, alignment, loc)
|
||||
case .Free:
|
||||
err = rb_free(stack, old_memory)
|
||||
|
||||
return nil, rb_free(stack, old_memory)
|
||||
case .Free_All:
|
||||
rb_free_all(stack)
|
||||
|
||||
case .Resize, .Resize_Non_Zeroed:
|
||||
assert(size >= 0, "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)
|
||||
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:])
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
case .Resize:
|
||||
return rb_resize_bytes(stack, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
case .Resize_Non_Zeroed:
|
||||
return rb_resize_bytes_non_zeroed(stack, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case .Query_Info:
|
||||
return nil, .Mode_Not_Implemented
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,50 +4,85 @@ package mem
|
||||
import "base:runtime"
|
||||
import "core:sync"
|
||||
|
||||
/*
|
||||
Allocation entry for the tracking allocator.
|
||||
|
||||
This structure stores the data related to an allocation.
|
||||
*/
|
||||
Tracking_Allocator_Entry :: struct {
|
||||
memory: rawptr,
|
||||
size: int,
|
||||
// Pointer to an allocated region.
|
||||
memory: rawptr,
|
||||
// Size of the allocated memory region.
|
||||
size: int,
|
||||
// Requested alignment.
|
||||
alignment: int,
|
||||
mode: Allocator_Mode,
|
||||
err: Allocator_Error,
|
||||
// Mode of the operation.
|
||||
mode: Allocator_Mode,
|
||||
// Error.
|
||||
err: Allocator_Error,
|
||||
// Location of the allocation.
|
||||
location: runtime.Source_Code_Location,
|
||||
}
|
||||
|
||||
/*
|
||||
Bad free entry for a tracking allocator.
|
||||
*/
|
||||
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,
|
||||
}
|
||||
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,
|
||||
total_memory_freed: i64,
|
||||
total_free_count: i64,
|
||||
peak_memory_allocated: i64,
|
||||
/*
|
||||
Tracking allocator data.
|
||||
*/
|
||||
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,
|
||||
total_memory_freed: i64,
|
||||
total_free_count: i64,
|
||||
peak_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) {
|
||||
t.backing = backing_allocator
|
||||
t.allocation_map.allocator = internals_allocator
|
||||
t.bad_free_array.allocator = internals_allocator
|
||||
|
||||
if .Free_All in query_features(t.backing) {
|
||||
t.clear_on_free_all = true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Destroy the tracking allocator.
|
||||
*/
|
||||
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
||||
delete(t.allocation_map)
|
||||
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) {
|
||||
sync.mutex_lock(&t.mutex)
|
||||
clear(&t.allocation_map)
|
||||
@@ -56,7 +91,11 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
||||
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) {
|
||||
sync.mutex_lock(&t.mutex)
|
||||
clear(&t.allocation_map)
|
||||
@@ -70,6 +109,39 @@ tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
|
||||
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)
|
||||
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
||||
return Allocator{
|
||||
@@ -78,9 +150,14 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
||||
}
|
||||
}
|
||||
|
||||
tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) {
|
||||
tracking_allocator_proc :: proc(
|
||||
allocator_data: rawptr,
|
||||
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) {
|
||||
data.total_memory_allocated += i64(entry.size)
|
||||
data.total_allocation_count += 1
|
||||
|
||||
@@ -6,7 +6,7 @@ import "core:mem"
|
||||
|
||||
expect_pool_allocation :: proc(t: ^testing.T, expected_used_bytes, num_bytes, alignment: int) {
|
||||
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)
|
||||
|
||||
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!")
|
||||
|
||||
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)
|
||||
|
||||
element, err := mem.alloc(num_bytes, allocator = pool_allocator)
|
||||
|
||||
Reference in New Issue
Block a user