mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-29 19:00:06 +00:00
Merge branch 'master' into file-tags-without-comments
This commit is contained in:
@@ -334,7 +334,7 @@ Inputs:
|
||||
Returns:
|
||||
- index: The index of the byte `c`, or -1 if it was not found.
|
||||
*/
|
||||
index_byte :: proc(s: []byte, c: byte) -> (index: int) #no_bounds_check {
|
||||
index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds_check {
|
||||
i, l := 0, len(s)
|
||||
|
||||
// Guard against small strings. On modern systems, it is ALWAYS
|
||||
@@ -469,7 +469,7 @@ Inputs:
|
||||
Returns:
|
||||
- index: The index of the byte `c`, or -1 if it was not found.
|
||||
*/
|
||||
last_index_byte :: proc(s: []byte, c: byte) -> int #no_bounds_check {
|
||||
last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_check {
|
||||
i := len(s)
|
||||
|
||||
// Guard against small strings. On modern systems, it is ALWAYS
|
||||
|
||||
+935
-79
File diff suppressed because it is too large
Load Diff
+2067
-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
|
||||
|
||||
@@ -2302,6 +2302,16 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
bd.name = name.text
|
||||
return bd
|
||||
|
||||
case "caller_expression":
|
||||
bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
|
||||
bd.tok = tok
|
||||
bd.name = name.text
|
||||
|
||||
if peek_token_kind(p, .Open_Paren) {
|
||||
return parse_call_expr(p, bd)
|
||||
}
|
||||
return bd
|
||||
|
||||
case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config":
|
||||
bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
|
||||
bd.tok = tok
|
||||
|
||||
@@ -920,7 +920,7 @@ get_page_size :: proc() -> int {
|
||||
_processor_core_count :: proc() -> int {
|
||||
count : int = 0
|
||||
count_size := size_of(count)
|
||||
if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 {
|
||||
if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 {
|
||||
if count > 0 {
|
||||
return count
|
||||
}
|
||||
|
||||
+19
-70
@@ -3,33 +3,38 @@ package os
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
foreign import "odin_env"
|
||||
|
||||
@(require_results)
|
||||
is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == '/' || c == '\\'
|
||||
}
|
||||
|
||||
Handle :: distinct u32
|
||||
|
||||
stdout: Handle = 1
|
||||
stderr: Handle = 2
|
||||
|
||||
@(require_results)
|
||||
open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
close :: proc(fd: Handle) -> Error {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
return nil
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
write :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
foreign odin_env {
|
||||
@(link_name="write")
|
||||
_write :: proc "contextless" (fd: Handle, p: []byte) ---
|
||||
}
|
||||
_write(fd, data)
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
read :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
@@ -45,19 +50,6 @@ file_size :: proc(fd: Handle) -> (i64, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
MAX_RW :: 1<<30
|
||||
|
||||
@(private)
|
||||
pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
@(private)
|
||||
pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
@@ -65,16 +57,6 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error)
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
stdout: Handle = 1
|
||||
stderr: Handle = 2
|
||||
|
||||
@(require_results)
|
||||
get_std_handle :: proc "contextless" (h: uint) -> Handle {
|
||||
context = runtime.default_context()
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
exists :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
@@ -90,9 +72,6 @@ is_dir :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
|
||||
//@private cwd_lock := win32.SRWLOCK{} // zero is initialized
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
@@ -118,18 +97,6 @@ remove_directory :: proc(path: string) -> (err: Error) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
is_abs :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
fix_long_path :: proc(path: string) -> string {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
link :: proc(old_name, new_name: string) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
@@ -169,7 +136,6 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
Handle :: distinct uintptr
|
||||
File_Time :: distinct u64
|
||||
|
||||
_Platform_Error :: enum i32 {
|
||||
@@ -254,12 +220,7 @@ WSAECONNRESET :: Platform_Error.WSAECONNRESET
|
||||
ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe
|
||||
ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir
|
||||
|
||||
// "Argv" arguments converted to Odin strings
|
||||
args := _alloc_command_line_arguments()
|
||||
|
||||
|
||||
|
||||
|
||||
args: []string
|
||||
|
||||
@(require_results)
|
||||
last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
|
||||
@@ -279,26 +240,14 @@ get_page_size :: proc() -> int {
|
||||
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
return 1
|
||||
}
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
context = runtime.default_context()
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
unimplemented_contextless("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
context = runtime.default_context()
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
_alloc_command_line_arguments :: proc() -> []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -978,7 +978,7 @@ get_page_size :: proc() -> int {
|
||||
_processor_core_count :: proc() -> int {
|
||||
count : int = 0
|
||||
count_size := size_of(count)
|
||||
if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 {
|
||||
if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 {
|
||||
if count > 0 {
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ Inputs:
|
||||
Returns:
|
||||
- res: A string created from the null-terminated byte pointer and length
|
||||
*/
|
||||
string_from_null_terminated_ptr :: proc(ptr: [^]byte, len: int) -> (res: string) {
|
||||
string_from_null_terminated_ptr :: proc "contextless" (ptr: [^]byte, len: int) -> (res: string) {
|
||||
s := string(ptr[:len])
|
||||
s = truncate_to_byte(s, 0)
|
||||
return s
|
||||
@@ -139,7 +139,7 @@ NOTE: Failure to find the byte results in returning the entire string.
|
||||
Returns:
|
||||
- res: The truncated string
|
||||
*/
|
||||
truncate_to_byte :: proc(str: string, b: byte) -> (res: string) {
|
||||
truncate_to_byte :: proc "contextless" (str: string, b: byte) -> (res: string) {
|
||||
n := index_byte(str, b)
|
||||
if n < 0 {
|
||||
n = len(str)
|
||||
@@ -261,7 +261,7 @@ Inputs:
|
||||
Returns:
|
||||
- result: `-1` if `lhs` comes first, `1` if `rhs` comes first, or `0` if they are equal
|
||||
*/
|
||||
compare :: proc(lhs, rhs: string) -> (result: int) {
|
||||
compare :: proc "contextless" (lhs, rhs: string) -> (result: int) {
|
||||
return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs)
|
||||
}
|
||||
/*
|
||||
@@ -1447,7 +1447,7 @@ Output:
|
||||
-1
|
||||
|
||||
*/
|
||||
index_byte :: proc(s: string, c: byte) -> (res: int) {
|
||||
index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
|
||||
return #force_inline bytes.index_byte(transmute([]u8)s, c)
|
||||
}
|
||||
/*
|
||||
@@ -1482,7 +1482,7 @@ Output:
|
||||
-1
|
||||
|
||||
*/
|
||||
last_index_byte :: proc(s: string, c: byte) -> (res: int) {
|
||||
last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
|
||||
return #force_inline bytes.last_index_byte(transmute([]u8)s, c)
|
||||
}
|
||||
/*
|
||||
@@ -1576,8 +1576,8 @@ Output:
|
||||
-1
|
||||
|
||||
*/
|
||||
index :: proc(s, substr: string) -> (res: int) {
|
||||
hash_str_rabin_karp :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) {
|
||||
index :: proc "contextless" (s, substr: string) -> (res: int) {
|
||||
hash_str_rabin_karp :: proc "contextless" (s: string) -> (hash: u32 = 0, pow: u32 = 1) {
|
||||
for i := 0; i < len(s); i += 1 {
|
||||
hash = hash*PRIME_RABIN_KARP + u32(s[i])
|
||||
}
|
||||
|
||||
+42
-41
@@ -22,19 +22,17 @@ Raw_Chan :: struct {
|
||||
allocator: runtime.Allocator,
|
||||
allocation_size: int,
|
||||
msg_size: u16,
|
||||
closed: b16, // atomic
|
||||
closed: b16, // guarded by `mutex`
|
||||
mutex: sync.Mutex,
|
||||
r_cond: sync.Cond,
|
||||
w_cond: sync.Cond,
|
||||
r_waiting: int, // atomic
|
||||
w_waiting: int, // atomic
|
||||
r_waiting: int, // guarded by `mutex`
|
||||
w_waiting: int, // guarded by `mutex`
|
||||
|
||||
// Buffered
|
||||
queue: ^Raw_Queue,
|
||||
|
||||
// Unbuffered
|
||||
r_mutex: sync.Mutex,
|
||||
w_mutex: sync.Mutex,
|
||||
unbuffered_data: rawptr,
|
||||
}
|
||||
|
||||
@@ -164,27 +162,30 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
|
||||
}
|
||||
if c.queue != nil { // buffered
|
||||
sync.guard(&c.mutex)
|
||||
for c.queue.len == c.queue.cap {
|
||||
sync.atomic_add(&c.w_waiting, 1)
|
||||
for !c.closed && c.queue.len == c.queue.cap {
|
||||
c.w_waiting += 1
|
||||
sync.wait(&c.w_cond, &c.mutex)
|
||||
sync.atomic_sub(&c.w_waiting, 1)
|
||||
c.w_waiting -= 1
|
||||
}
|
||||
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
ok = raw_queue_push(c.queue, msg_in)
|
||||
if sync.atomic_load(&c.r_waiting) > 0 {
|
||||
if c.r_waiting > 0 {
|
||||
sync.signal(&c.r_cond)
|
||||
}
|
||||
} else if c.unbuffered_data != nil { // unbuffered
|
||||
sync.guard(&c.w_mutex)
|
||||
sync.guard(&c.mutex)
|
||||
|
||||
if sync.atomic_load(&c.closed) {
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
|
||||
sync.atomic_add(&c.w_waiting, 1)
|
||||
if sync.atomic_load(&c.r_waiting) > 0 {
|
||||
c.w_waiting += 1
|
||||
if c.r_waiting > 0 {
|
||||
sync.signal(&c.r_cond)
|
||||
}
|
||||
sync.wait(&c.w_cond, &c.mutex)
|
||||
@@ -201,13 +202,13 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
|
||||
if c.queue != nil { // buffered
|
||||
sync.guard(&c.mutex)
|
||||
for c.queue.len == 0 {
|
||||
if sync.atomic_load(&c.closed) {
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
|
||||
sync.atomic_add(&c.r_waiting, 1)
|
||||
c.r_waiting += 1
|
||||
sync.wait(&c.r_cond, &c.mutex)
|
||||
sync.atomic_sub(&c.r_waiting, 1)
|
||||
c.r_waiting -= 1
|
||||
}
|
||||
|
||||
msg := raw_queue_pop(c.queue)
|
||||
@@ -215,27 +216,26 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
|
||||
mem.copy(msg_out, msg, int(c.msg_size))
|
||||
}
|
||||
|
||||
if sync.atomic_load(&c.w_waiting) > 0 {
|
||||
if c.w_waiting > 0 {
|
||||
sync.signal(&c.w_cond)
|
||||
}
|
||||
ok = true
|
||||
} else if c.unbuffered_data != nil { // unbuffered
|
||||
sync.guard(&c.r_mutex)
|
||||
sync.guard(&c.mutex)
|
||||
|
||||
for !sync.atomic_load(&c.closed) &&
|
||||
sync.atomic_load(&c.w_waiting) == 0 {
|
||||
sync.atomic_add(&c.r_waiting, 1)
|
||||
for !c.closed &&
|
||||
c.w_waiting == 0 {
|
||||
c.r_waiting += 1
|
||||
sync.wait(&c.r_cond, &c.mutex)
|
||||
sync.atomic_sub(&c.r_waiting, 1)
|
||||
c.r_waiting -= 1
|
||||
}
|
||||
|
||||
if sync.atomic_load(&c.closed) {
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
|
||||
mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
|
||||
sync.atomic_sub(&c.w_waiting, 1)
|
||||
c.w_waiting -= 1
|
||||
|
||||
sync.signal(&c.w_cond)
|
||||
ok = true
|
||||
@@ -255,21 +255,24 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool)
|
||||
return false
|
||||
}
|
||||
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
ok = raw_queue_push(c.queue, msg_in)
|
||||
if sync.atomic_load(&c.r_waiting) > 0 {
|
||||
if c.r_waiting > 0 {
|
||||
sync.signal(&c.r_cond)
|
||||
}
|
||||
} else if c.unbuffered_data != nil { // unbuffered
|
||||
sync.guard(&c.w_mutex)
|
||||
sync.guard(&c.mutex)
|
||||
|
||||
if sync.atomic_load(&c.closed) {
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
|
||||
sync.atomic_add(&c.w_waiting, 1)
|
||||
if sync.atomic_load(&c.r_waiting) > 0 {
|
||||
c.w_waiting += 1
|
||||
if c.r_waiting > 0 {
|
||||
sync.signal(&c.r_cond)
|
||||
}
|
||||
sync.wait(&c.w_cond, &c.mutex)
|
||||
@@ -294,21 +297,19 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool {
|
||||
mem.copy(msg_out, msg, int(c.msg_size))
|
||||
}
|
||||
|
||||
if sync.atomic_load(&c.w_waiting) > 0 {
|
||||
if c.w_waiting > 0 {
|
||||
sync.signal(&c.w_cond)
|
||||
}
|
||||
return true
|
||||
} else if c.unbuffered_data != nil { // unbuffered
|
||||
sync.guard(&c.r_mutex)
|
||||
sync.guard(&c.mutex)
|
||||
|
||||
if sync.atomic_load(&c.closed) ||
|
||||
sync.atomic_load(&c.w_waiting) == 0 {
|
||||
if c.closed || c.w_waiting == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
|
||||
sync.atomic_sub(&c.w_waiting, 1)
|
||||
c.w_waiting -= 1
|
||||
|
||||
sync.signal(&c.w_cond)
|
||||
return true
|
||||
@@ -351,10 +352,10 @@ close :: proc "contextless" (c: ^Raw_Chan) -> bool {
|
||||
return false
|
||||
}
|
||||
sync.guard(&c.mutex)
|
||||
if sync.atomic_load(&c.closed) {
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
sync.atomic_store(&c.closed, true)
|
||||
c.closed = true
|
||||
sync.broadcast(&c.r_cond)
|
||||
sync.broadcast(&c.w_cond)
|
||||
return true
|
||||
@@ -366,7 +367,7 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool {
|
||||
return true
|
||||
}
|
||||
sync.guard(&c.mutex)
|
||||
return bool(sync.atomic_load(&c.closed))
|
||||
return bool(c.closed)
|
||||
}
|
||||
|
||||
|
||||
@@ -423,9 +424,9 @@ raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) {
|
||||
can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
|
||||
sync.guard(&c.mutex)
|
||||
if is_buffered(c) {
|
||||
return len(c) > 0
|
||||
return c.queue.len > 0
|
||||
}
|
||||
return sync.atomic_load(&c.w_waiting) > 0
|
||||
return c.w_waiting > 0
|
||||
}
|
||||
|
||||
|
||||
@@ -435,7 +436,7 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
|
||||
if is_buffered(c) {
|
||||
return c.queue.len < c.queue.cap
|
||||
}
|
||||
return sync.atomic_load(&c.r_waiting) > 0
|
||||
return c.w_waiting == 0
|
||||
}
|
||||
|
||||
|
||||
@@ -484,4 +485,4 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []
|
||||
ok = send_raw(sends[sel.idx], send_msgs[sel.idx])
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+38
-37
@@ -8,7 +8,7 @@ _ :: vg
|
||||
Wait group.
|
||||
|
||||
Wait group is a synchronization primitive used by the waiting thread to wait,
|
||||
until a all working threads finish work.
|
||||
until all working threads finish work.
|
||||
|
||||
The waiting thread first sets the number of working threads it will expect to
|
||||
wait for using `wait_group_add` call, and start waiting using `wait_group_wait`
|
||||
@@ -35,7 +35,7 @@ Wait_Group :: struct #no_copy {
|
||||
/*
|
||||
Increment an internal counter of a wait group.
|
||||
|
||||
This procedure atomicaly increments a number to the specified wait group's
|
||||
This procedure atomically increments a number to the specified wait group's
|
||||
internal counter by a specified amount. This operation can be done on any
|
||||
thread.
|
||||
*/
|
||||
@@ -48,12 +48,12 @@ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) {
|
||||
|
||||
atomic_add(&wg.counter, delta)
|
||||
if wg.counter < 0 {
|
||||
_panic("sync.Wait_Group negative counter")
|
||||
panic_contextless("sync.Wait_Group negative counter")
|
||||
}
|
||||
if wg.counter == 0 {
|
||||
cond_broadcast(&wg.cond)
|
||||
if wg.counter != 0 {
|
||||
_panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait")
|
||||
panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ wait_group_wait :: proc "contextless" (wg: ^Wait_Group) {
|
||||
if wg.counter != 0 {
|
||||
cond_wait(&wg.cond, &wg.mutex)
|
||||
if wg.counter != 0 {
|
||||
_panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait")
|
||||
panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: t
|
||||
return false
|
||||
}
|
||||
if wg.counter != 0 {
|
||||
_panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait")
|
||||
panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait")
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -121,7 +121,7 @@ When `barrier_wait` procedure is called by any thread, that thread will block
|
||||
the execution, until all threads associated with the barrier reach the same
|
||||
point of execution and also call `barrier_wait`.
|
||||
|
||||
when barrier is initialized, a `thread_count` parameter is passed, signifying
|
||||
When a barrier is initialized, a `thread_count` parameter is passed, signifying
|
||||
the amount of participant threads of the barrier. The barrier also keeps track
|
||||
of an internal atomic counter. When a thread calls `barrier_wait`, the internal
|
||||
counter is incremented. When the internal counter reaches `thread_count`, it is
|
||||
@@ -208,7 +208,7 @@ Represents a thread synchronization primitive that, when signalled, releases one
|
||||
single waiting thread and then resets automatically to a state where it can be
|
||||
signalled again.
|
||||
|
||||
When a thread calls `auto_reset_event_wait`, it's execution will be blocked,
|
||||
When a thread calls `auto_reset_event_wait`, its execution will be blocked,
|
||||
until the event is signalled by another thread. The call to
|
||||
`auto_reset_event_signal` wakes up exactly one thread waiting for the event.
|
||||
*/
|
||||
@@ -228,15 +228,15 @@ thread.
|
||||
*/
|
||||
auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) {
|
||||
old_status := atomic_load_explicit(&e.status, .Relaxed)
|
||||
new_status := old_status + 1 if old_status < 1 else 1
|
||||
for {
|
||||
new_status := old_status + 1 if old_status < 1 else 1
|
||||
if _, ok := atomic_compare_exchange_weak_explicit(&e.status, old_status, new_status, .Release, .Relaxed); ok {
|
||||
break
|
||||
}
|
||||
|
||||
if old_status < 0 {
|
||||
sema_post(&e.sema)
|
||||
}
|
||||
cpu_relax()
|
||||
}
|
||||
if old_status < 0 {
|
||||
sema_post(&e.sema)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ waiting to acquire the lock, exactly one of those threads is unblocked and
|
||||
allowed into the critical section.
|
||||
*/
|
||||
ticket_mutex_unlock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) {
|
||||
atomic_add_explicit(&m.serving, 1, .Relaxed)
|
||||
atomic_add_explicit(&m.serving, 1, .Release)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -331,8 +331,8 @@ Benaphore.
|
||||
|
||||
A benaphore is a combination of an atomic variable and a semaphore that can
|
||||
improve locking efficiency in a no-contention system. Acquiring a benaphore
|
||||
lock doesn't call into an internal semaphore, if no other thread in a middle of
|
||||
a critical section.
|
||||
lock doesn't call into an internal semaphore, if no other thread is in the
|
||||
middle of a critical section.
|
||||
|
||||
Once a lock on a benaphore is acquired by a thread, no other thread is allowed
|
||||
into any critical sections, associted with the same benaphore, until the lock
|
||||
@@ -355,7 +355,7 @@ from entering any critical sections associated with the same benaphore, until
|
||||
until the lock is released.
|
||||
*/
|
||||
benaphore_lock :: proc "contextless" (b: ^Benaphore) {
|
||||
if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 {
|
||||
if atomic_add_explicit(&b.counter, 1, .Acquire) > 0 {
|
||||
sema_wait(&b.sema)
|
||||
}
|
||||
}
|
||||
@@ -381,10 +381,10 @@ Release a lock on a benaphore.
|
||||
|
||||
This procedure releases a lock on the specified benaphore. If any of the threads
|
||||
are waiting on the lock, exactly one thread is allowed into a critical section
|
||||
associated with the same banaphore.
|
||||
associated with the same benaphore.
|
||||
*/
|
||||
benaphore_unlock :: proc "contextless" (b: ^Benaphore) {
|
||||
if atomic_sub_explicit(&b.counter, 1, .Release) > 0 {
|
||||
if atomic_sub_explicit(&b.counter, 1, .Release) > 1 {
|
||||
sema_post(&b.sema)
|
||||
}
|
||||
}
|
||||
@@ -418,8 +418,8 @@ benaphore_guard :: proc "contextless" (m: ^Benaphore) -> bool {
|
||||
/*
|
||||
Recursive benaphore.
|
||||
|
||||
Recurisve benaphore is just like a plain benaphore, except it allows reentrancy
|
||||
into the critical section.
|
||||
A recursive benaphore is just like a plain benaphore, except it allows
|
||||
reentrancy into the critical section.
|
||||
|
||||
When a lock is acquired on a benaphore, all other threads attempting to
|
||||
acquire a lock on the same benaphore will be blocked from any critical sections,
|
||||
@@ -449,13 +449,15 @@ recursive benaphore, until the lock is released.
|
||||
*/
|
||||
recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) {
|
||||
tid := current_thread_id()
|
||||
if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 {
|
||||
if tid != b.owner {
|
||||
sema_wait(&b.sema)
|
||||
check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) {
|
||||
atomic_add_explicit(&b.counter, 1, .Relaxed)
|
||||
if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok {
|
||||
break check_owner
|
||||
}
|
||||
sema_wait(&b.sema)
|
||||
atomic_store_explicit(&b.owner, tid, .Release)
|
||||
}
|
||||
// inside the lock
|
||||
b.owner = tid
|
||||
b.recursion += 1
|
||||
}
|
||||
|
||||
@@ -472,15 +474,14 @@ benaphore, until the lock is released.
|
||||
*/
|
||||
recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) -> bool {
|
||||
tid := current_thread_id()
|
||||
if b.owner == tid {
|
||||
atomic_add_explicit(&b.counter, 1, .Acquire)
|
||||
}
|
||||
|
||||
if v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 0, 1, .Acquire, .Acquire); v != 0 {
|
||||
check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) {
|
||||
if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok {
|
||||
atomic_add_explicit(&b.counter, 1, .Relaxed)
|
||||
break check_owner
|
||||
}
|
||||
return false
|
||||
}
|
||||
// inside the lock
|
||||
b.owner = tid
|
||||
b.recursion += 1
|
||||
return true
|
||||
}
|
||||
@@ -494,14 +495,14 @@ for other threads for entering.
|
||||
*/
|
||||
recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) {
|
||||
tid := current_thread_id()
|
||||
_assert(tid == b.owner, "tid != b.owner")
|
||||
assert_contextless(tid == atomic_load_explicit(&b.owner, .Relaxed), "tid != b.owner")
|
||||
b.recursion -= 1
|
||||
recursion := b.recursion
|
||||
|
||||
if recursion == 0 {
|
||||
b.owner = 0
|
||||
}
|
||||
if atomic_sub_explicit(&b.counter, 1, .Release) > 0 {
|
||||
if recursion == 0 {
|
||||
if atomic_sub_explicit(&b.counter, 1, .Relaxed) == 1 {
|
||||
atomic_store_explicit(&b.owner, 0, .Release)
|
||||
} else {
|
||||
sema_post(&b.sema)
|
||||
}
|
||||
}
|
||||
@@ -740,4 +741,4 @@ Make event available.
|
||||
one_shot_event_signal :: proc "contextless" (e: ^One_Shot_Event) {
|
||||
atomic_store_explicit(&e.state, 1, .Release)
|
||||
futex_broadcast(&e.state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ foreign System {
|
||||
// __ulock_wait is not available on 10.15
|
||||
// See https://github.com/odin-lang/Odin/issues/1959
|
||||
__ulock_wait :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_us: u32) -> c.int ---
|
||||
// >= MacOS 11.
|
||||
__ulock_wait2 :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_ns: u64, value2: u64) -> c.int ---
|
||||
__ulock_wake :: proc "c" (operation: u32, addr: rawptr, wake_value: u64) -> c.int ---
|
||||
}
|
||||
|
||||
@@ -48,22 +50,29 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
|
||||
case -ETIMEDOUT:
|
||||
return false
|
||||
case:
|
||||
_panic("darwin.os_sync_wait_on_address_with_timeout failure")
|
||||
panic_contextless("darwin.os_sync_wait_on_address_with_timeout failure")
|
||||
}
|
||||
} else {
|
||||
|
||||
timeout_ns := u32(duration)
|
||||
s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
|
||||
when darwin.ULOCK_WAIT_2_AVAILABLE {
|
||||
timeout_ns := u64(duration)
|
||||
s := __ulock_wait2(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns, 0)
|
||||
} else {
|
||||
timeout_us := u32(duration / time.Microsecond)
|
||||
s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_us)
|
||||
}
|
||||
|
||||
if s >= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
switch s {
|
||||
case EINTR, EFAULT:
|
||||
return true
|
||||
case ETIMEDOUT:
|
||||
return false
|
||||
case:
|
||||
_panic("futex_wait failure")
|
||||
panic_contextless("futex_wait failure")
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -83,7 +92,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) {
|
||||
case -ENOENT:
|
||||
return
|
||||
case:
|
||||
_panic("darwin.os_sync_wake_by_address_any failure")
|
||||
panic_contextless("darwin.os_sync_wake_by_address_any failure")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -99,7 +108,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) {
|
||||
case ENOENT:
|
||||
return
|
||||
case:
|
||||
_panic("futex_wake_single failure")
|
||||
panic_contextless("futex_wake_single failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +128,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) {
|
||||
case -ENOENT:
|
||||
return
|
||||
case:
|
||||
_panic("darwin.os_sync_wake_by_address_all failure")
|
||||
panic_contextless("darwin.os_sync_wake_by_address_all failure")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -135,7 +144,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) {
|
||||
case ENOENT:
|
||||
return
|
||||
case:
|
||||
_panic("futex_wake_all failure")
|
||||
panic_contextless("futex_wake_all failure")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
|
||||
continue
|
||||
}
|
||||
|
||||
_panic("_futex_wait failure")
|
||||
panic_contextless("_futex_wait failure")
|
||||
}
|
||||
|
||||
unreachable()
|
||||
@@ -44,14 +44,14 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
|
||||
return false
|
||||
}
|
||||
|
||||
_panic("_futex_wait_with_timeout failure")
|
||||
panic_contextless("_futex_wait_with_timeout failure")
|
||||
}
|
||||
|
||||
_futex_signal :: proc "contextless" (f: ^Futex) {
|
||||
errno := freebsd._umtx_op(f, .WAKE, 1, nil, nil)
|
||||
|
||||
if errno != nil {
|
||||
_panic("_futex_signal failure")
|
||||
panic_contextless("_futex_signal failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) {
|
||||
errno := freebsd._umtx_op(f, .WAKE, cast(c.ulong)max(i32), nil, nil)
|
||||
|
||||
if errno != nil {
|
||||
_panic("_futex_broadcast failure")
|
||||
panic_contextless("_futex_broadcast failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool {
|
||||
return true
|
||||
case:
|
||||
// TODO(flysand): More descriptive panic messages based on the vlaue of `errno`
|
||||
_panic("futex_wait failure")
|
||||
panic_contextless("futex_wait failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du
|
||||
case .NONE, .EINTR, .EAGAIN:
|
||||
return true
|
||||
case:
|
||||
_panic("futex_wait_with_timeout failure")
|
||||
panic_contextless("futex_wait_with_timeout failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ _futex_signal :: proc "contextless" (futex: ^Futex) {
|
||||
case .NONE:
|
||||
return
|
||||
case:
|
||||
_panic("futex_wake_single failure")
|
||||
panic_contextless("futex_wake_single failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,6 @@ _futex_broadcast :: proc "contextless" (futex: ^Futex) {
|
||||
case .NONE:
|
||||
return
|
||||
case:
|
||||
_panic("_futex_wake_all failure")
|
||||
panic_contextless("_futex_wake_all failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool {
|
||||
case EINTR, EAGAIN:
|
||||
return true
|
||||
case:
|
||||
_panic("futex_wait failure")
|
||||
panic_contextless("futex_wait failure")
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -55,7 +55,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du
|
||||
case ETIMEDOUT:
|
||||
return false
|
||||
case:
|
||||
_panic("futex_wait_with_timeout failure")
|
||||
panic_contextless("futex_wait_with_timeout failure")
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -63,12 +63,12 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du
|
||||
|
||||
_futex_signal :: proc "contextless" (futex: ^Futex) {
|
||||
if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); !ok {
|
||||
_panic("futex_wake_single failure")
|
||||
panic_contextless("futex_wake_single failure")
|
||||
}
|
||||
}
|
||||
|
||||
_futex_broadcast :: proc "contextless" (futex: ^Futex) {
|
||||
if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0); !ok {
|
||||
_panic("_futex_wake_all failure")
|
||||
panic_contextless("_futex_wake_all failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
_panic("futex_wait failure")
|
||||
panic_contextless("futex_wait failure")
|
||||
}
|
||||
|
||||
_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
|
||||
@@ -62,14 +62,14 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
|
||||
return false
|
||||
}
|
||||
|
||||
_panic("futex_wait_with_timeout failure")
|
||||
panic_contextless("futex_wait_with_timeout failure")
|
||||
}
|
||||
|
||||
_futex_signal :: proc "contextless" (f: ^Futex) {
|
||||
res := _unix_futex(f, FUTEX_WAKE_PRIVATE, 1, nil)
|
||||
|
||||
if res == -1 {
|
||||
_panic("futex_wake_single failure")
|
||||
panic_contextless("futex_wake_single failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) {
|
||||
res := _unix_futex(f, FUTEX_WAKE_PRIVATE, u32(max(i32)), nil)
|
||||
|
||||
if res == -1 {
|
||||
_panic("_futex_wake_all failure")
|
||||
panic_contextless("_futex_wake_all failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import "core:time"
|
||||
|
||||
_futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
|
||||
when !intrinsics.has_target_feature("atomics") {
|
||||
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
} else {
|
||||
s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
|
||||
return s != 0
|
||||
@@ -19,7 +19,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
|
||||
|
||||
_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
|
||||
when !intrinsics.has_target_feature("atomics") {
|
||||
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
} else {
|
||||
s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration))
|
||||
return s != 0
|
||||
@@ -28,7 +28,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
|
||||
|
||||
_futex_signal :: proc "contextless" (f: ^Futex) {
|
||||
when !intrinsics.has_target_feature("atomics") {
|
||||
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
} else {
|
||||
loop: for {
|
||||
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
|
||||
@@ -41,7 +41,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) {
|
||||
|
||||
_futex_broadcast :: proc "contextless" (f: ^Futex) {
|
||||
when !intrinsics.has_target_feature("atomics") {
|
||||
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
|
||||
} else {
|
||||
loop: for {
|
||||
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package sync
|
||||
|
||||
import "base:runtime"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
@@ -390,7 +389,7 @@ recursive_mutex_guard :: proc "contextless" (m: ^Recursive_Mutex) -> bool {
|
||||
A condition variable.
|
||||
|
||||
`Cond` implements a condition variable, a rendezvous point for threads waiting
|
||||
for signalling the occurence of an event. Condition variables are used on
|
||||
for signalling the occurence of an event. Condition variables are used in
|
||||
conjuction with mutexes to provide a shared access to one or more shared
|
||||
variable.
|
||||
|
||||
@@ -560,7 +559,7 @@ futex_wait :: proc "contextless" (f: ^Futex, expected: u32) {
|
||||
return
|
||||
}
|
||||
ok := _futex_wait(f, expected)
|
||||
_assert(ok, "futex_wait failure")
|
||||
assert_contextless(ok, "futex_wait failure")
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -597,18 +596,3 @@ Wake up multiple threads waiting on a futex.
|
||||
futex_broadcast :: proc "contextless" (f: ^Futex) {
|
||||
_futex_broadcast(f)
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
_assert :: proc "contextless" (cond: bool, msg: string) {
|
||||
if !cond {
|
||||
_panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_panic :: proc "contextless" (msg: string) -> ! {
|
||||
runtime.print_string(msg)
|
||||
runtime.print_byte('\n')
|
||||
runtime.trap()
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ atomic_recursive_mutex_lock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) {
|
||||
|
||||
atomic_recursive_mutex_unlock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) {
|
||||
tid := current_thread_id()
|
||||
_assert(tid == m.owner, "tid != m.owner")
|
||||
assert_contextless(tid == m.owner, "tid != m.owner")
|
||||
m.recursion -= 1
|
||||
recursion := m.recursion
|
||||
if recursion == 0 {
|
||||
@@ -361,7 +361,7 @@ atomic_sema_wait_with_timeout :: proc "contextless" (s: ^Atomic_Sema, duration:
|
||||
if !futex_wait_with_timeout(&s.count, u32(original_count), remaining) {
|
||||
return false
|
||||
}
|
||||
original_count = s.count
|
||||
original_count = atomic_load_explicit(&s.count, .Relaxed)
|
||||
}
|
||||
if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) {
|
||||
return true
|
||||
|
||||
@@ -5,6 +5,7 @@ foreign import system "system:System.framework"
|
||||
// #define OS_WAIT_ON_ADDR_AVAILABILITY \
|
||||
// __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4))
|
||||
when ODIN_OS == .Darwin {
|
||||
|
||||
when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 17_04_00 {
|
||||
WAIT_ON_ADDRESS_AVAILABLE :: true
|
||||
} else when ODIN_MINIMUM_OS_VERSION >= 14_04_00 {
|
||||
@@ -12,8 +13,18 @@ when ODIN_OS == .Darwin {
|
||||
} else {
|
||||
WAIT_ON_ADDRESS_AVAILABLE :: false
|
||||
}
|
||||
|
||||
when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 14_00_00 {
|
||||
ULOCK_WAIT_2_AVAILABLE :: true
|
||||
} else when ODIN_MINIMUM_OS_VERSION >= 11_00_00 {
|
||||
ULOCK_WAIT_2_AVAILABLE :: true
|
||||
} else {
|
||||
ULOCK_WAIT_2_AVAILABLE :: false
|
||||
}
|
||||
|
||||
} else {
|
||||
WAIT_ON_ADDRESS_AVAILABLE :: false
|
||||
ULOCK_WAIT_2_AVAILABLE :: false
|
||||
}
|
||||
|
||||
os_sync_wait_on_address_flag :: enum u32 {
|
||||
|
||||
@@ -530,6 +530,10 @@ macos_release_map: map[string]Darwin_To_Release = {
|
||||
"23F79" = {{23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}},
|
||||
"23G80" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}},
|
||||
"23G93" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}},
|
||||
"23H124" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 7, 0}}},
|
||||
|
||||
// MacOS Sequoia
|
||||
"24A335" = {{24, 0, 0}, "macOS", {"Sequoia", {15, 0, 0}}},
|
||||
}
|
||||
|
||||
@(private)
|
||||
|
||||
@@ -204,6 +204,10 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
console_ansi_init()
|
||||
}
|
||||
|
||||
stdout := io.to_writer(os.stream_from_handle(os.stdout))
|
||||
stderr := io.to_writer(os.stream_from_handle(os.stderr))
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//+private
|
||||
package testing
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
console_ansi_init :: proc() {
|
||||
stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
|
||||
if stdout != win32.INVALID_HANDLE && stdout != nil {
|
||||
old_console_mode: u32
|
||||
if win32.GetConsoleMode(stdout, &old_console_mode) {
|
||||
win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
|
||||
if stderr != win32.INVALID_HANDLE && stderr != nil {
|
||||
old_console_mode: u32
|
||||
if win32.GetConsoleMode(stderr, &old_console_mode) {
|
||||
win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import "core:os"
|
||||
|
||||
@(private="file", thread_local)
|
||||
local_test_index: libc.sig_atomic_t
|
||||
@(private="file", thread_local)
|
||||
local_test_index_set: bool
|
||||
|
||||
// Windows does not appear to have a SIGTRAP, so this is defined here, instead
|
||||
// of in the libc package, just so there's no confusion about it being
|
||||
@@ -45,6 +47,13 @@ stop_runner_callback :: proc "c" (sig: libc.int) {
|
||||
|
||||
@(private="file")
|
||||
stop_test_callback :: proc "c" (sig: libc.int) {
|
||||
if !local_test_index_set {
|
||||
// We're a thread created by a test thread.
|
||||
//
|
||||
// There's nothing we can do to inform the test runner about who
|
||||
// signalled, so hopefully the test will handle their own sub-threads.
|
||||
return
|
||||
}
|
||||
if local_test_index == -1 {
|
||||
// We're the test runner, and we ourselves have caught a signal from
|
||||
// which there is no recovery.
|
||||
@@ -114,6 +123,7 @@ This is a dire bug and should be reported to the Odin developers.
|
||||
|
||||
_setup_signal_handler :: proc() {
|
||||
local_test_index = -1
|
||||
local_test_index_set = true
|
||||
|
||||
// Catch user interrupt / CTRL-C.
|
||||
libc.signal(libc.SIGINT, stop_runner_callback)
|
||||
@@ -135,6 +145,7 @@ _setup_signal_handler :: proc() {
|
||||
|
||||
_setup_task_signal_handler :: proc(test_index: int) {
|
||||
local_test_index = cast(libc.sig_atomic_t)test_index
|
||||
local_test_index_set = true
|
||||
}
|
||||
|
||||
_should_stop_runner :: proc() -> bool {
|
||||
|
||||
@@ -105,9 +105,13 @@ cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) {
|
||||
append(&t.cleanups, Internal_Cleanup{procedure, user_data, context})
|
||||
}
|
||||
|
||||
expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool {
|
||||
expect :: proc(t: ^T, ok: bool, msg := "", expr := #caller_expression(ok), loc := #caller_location) -> bool {
|
||||
if !ok {
|
||||
log.error(msg, location=loc)
|
||||
if msg == "" {
|
||||
log.errorf("expected %v to be true", expr, location=loc)
|
||||
} else {
|
||||
log.error(msg, location=loc)
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
@@ -119,10 +123,10 @@ expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_loc
|
||||
return ok
|
||||
}
|
||||
|
||||
expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) {
|
||||
expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location, value_expr := #caller_expression(value)) -> bool where intrinsics.type_is_comparable(T) {
|
||||
ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected)
|
||||
if !ok {
|
||||
log.errorf("expected %v, got %v", expected, value, location=loc)
|
||||
log.errorf("expected %v to be %v, got %v", value_expr, expected, value, location=loc)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil,
|
||||
t := create(thread_proc, priority)
|
||||
t.data = rawptr(fn)
|
||||
if self_cleanup {
|
||||
t.flags += {.Self_Cleanup}
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
}
|
||||
t.init_context = init_context
|
||||
start(t)
|
||||
@@ -307,7 +307,7 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co
|
||||
t.user_index = 1
|
||||
t.user_args[0] = data
|
||||
if self_cleanup {
|
||||
t.flags += {.Self_Cleanup}
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
}
|
||||
t.init_context = init_context
|
||||
start(t)
|
||||
@@ -347,7 +347,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex
|
||||
mem.copy(&t.user_args[0], &data, size_of(T))
|
||||
|
||||
if self_cleanup {
|
||||
t.flags += {.Self_Cleanup}
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
}
|
||||
|
||||
t.init_context = init_context
|
||||
@@ -394,7 +394,7 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2),
|
||||
_ = copy(user_args[n:], mem.ptr_to_bytes(&arg2))
|
||||
|
||||
if self_cleanup {
|
||||
t.flags += {.Self_Cleanup}
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
}
|
||||
|
||||
t.init_context = init_context
|
||||
@@ -443,7 +443,7 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr
|
||||
_ = copy(user_args[n:], mem.ptr_to_bytes(&arg3))
|
||||
|
||||
if self_cleanup {
|
||||
t.flags += {.Self_Cleanup}
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
}
|
||||
|
||||
t.init_context = init_context
|
||||
@@ -494,7 +494,7 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4:
|
||||
_ = copy(user_args[n:], mem.ptr_to_bytes(&arg4))
|
||||
|
||||
if self_cleanup {
|
||||
t.flags += {.Self_Cleanup}
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
}
|
||||
|
||||
t.init_context = init_context
|
||||
|
||||
@@ -60,6 +60,7 @@ pool_thread_runner :: proc(t: ^Thread) {
|
||||
if task, ok := pool_pop_waiting(pool); ok {
|
||||
data.task = task
|
||||
pool_do_work(pool, task)
|
||||
sync.guard(&pool.mutex)
|
||||
data.task = {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,14 @@ package thread
|
||||
import "base:runtime"
|
||||
import "core:sync"
|
||||
import "core:sys/unix"
|
||||
import "core:time"
|
||||
|
||||
_IS_SUPPORTED :: true
|
||||
|
||||
CAS :: sync.atomic_compare_exchange_strong
|
||||
|
||||
// NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t.
|
||||
// Also see core/sys/darwin/mach_darwin.odin/semaphore_t.
|
||||
Thread_Os_Specific :: struct #align(16) {
|
||||
unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux.
|
||||
cond: sync.Cond,
|
||||
mutex: sync.Mutex,
|
||||
start_ok: sync.Sema,
|
||||
}
|
||||
//
|
||||
// Creates a thread which will run the given procedure.
|
||||
@@ -29,14 +25,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
|
||||
// We need to give the thread a moment to start up before we enable cancellation.
|
||||
can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) == 0
|
||||
|
||||
sync.lock(&t.mutex)
|
||||
|
||||
t.id = sync.current_thread_id()
|
||||
|
||||
for (.Started not_in sync.atomic_load(&t.flags)) {
|
||||
// HACK: use a timeout so in the event that the condition is signalled at THIS comment's exact point
|
||||
// (after checking flags, before starting the wait) it gets itself out of that deadlock after a ms.
|
||||
sync.wait_with_timeout(&t.cond, &t.mutex, time.Millisecond)
|
||||
if .Started not_in sync.atomic_load(&t.flags) {
|
||||
sync.wait(&t.start_ok)
|
||||
}
|
||||
|
||||
if .Joined in sync.atomic_load(&t.flags) {
|
||||
@@ -66,8 +58,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
|
||||
|
||||
sync.atomic_or(&t.flags, { .Done })
|
||||
|
||||
sync.unlock(&t.mutex)
|
||||
|
||||
if .Self_Cleanup in sync.atomic_load(&t.flags) {
|
||||
res := unix.pthread_detach(t.unix_thread)
|
||||
assert_contextless(res == 0)
|
||||
@@ -132,7 +122,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
|
||||
|
||||
_start :: proc(t: ^Thread) {
|
||||
sync.atomic_or(&t.flags, { .Started })
|
||||
sync.signal(&t.cond)
|
||||
sync.post(&t.start_ok)
|
||||
}
|
||||
|
||||
_is_done :: proc(t: ^Thread) -> bool {
|
||||
@@ -140,24 +130,18 @@ _is_done :: proc(t: ^Thread) -> bool {
|
||||
}
|
||||
|
||||
_join :: proc(t: ^Thread) {
|
||||
// sync.guard(&t.mutex)
|
||||
|
||||
if unix.pthread_equal(unix.pthread_self(), t.unix_thread) {
|
||||
return
|
||||
}
|
||||
|
||||
// Preserve other flags besides `.Joined`, like `.Started`.
|
||||
unjoined := sync.atomic_load(&t.flags) - {.Joined}
|
||||
joined := unjoined + {.Joined}
|
||||
|
||||
// Try to set `t.flags` from unjoined to joined. If it returns joined,
|
||||
// it means the previous value had that flag set and we can return.
|
||||
if res, ok := CAS(&t.flags, unjoined, joined); res == joined && !ok {
|
||||
// If the previous value was already `Joined`, then we can return.
|
||||
if .Joined in sync.atomic_or(&t.flags, {.Joined}) {
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent non-started threads from blocking main thread with initial wait
|
||||
// condition.
|
||||
if .Started not_in unjoined {
|
||||
if .Started not_in sync.atomic_load(&t.flags) {
|
||||
_start(t)
|
||||
}
|
||||
unix.pthread_join(t.unix_thread, nil)
|
||||
|
||||
@@ -27,7 +27,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
|
||||
__windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD {
|
||||
t := (^Thread)(t_)
|
||||
|
||||
if .Joined in t.flags {
|
||||
if .Joined in sync.atomic_load(&t.flags) {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -48,9 +48,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
|
||||
t.procedure(t)
|
||||
}
|
||||
|
||||
intrinsics.atomic_store(&t.flags, t.flags + {.Done})
|
||||
intrinsics.atomic_or(&t.flags, {.Done})
|
||||
|
||||
if .Self_Cleanup in t.flags {
|
||||
if .Self_Cleanup in sync.atomic_load(&t.flags) {
|
||||
win32.CloseHandle(t.win32_thread)
|
||||
t.win32_thread = win32.INVALID_HANDLE
|
||||
// NOTE(ftphikari): It doesn't matter which context 'free' received, right?
|
||||
|
||||
Reference in New Issue
Block a user