mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 07:38:48 +00:00
Merge pull request #5122 from Lperlind/asan-allocators
Add asan support for various allocators and stack unpoisoning
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
|
import "base:sanitizer"
|
||||||
|
|
||||||
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
|
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
|
||||||
|
|
||||||
@@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
|
|||||||
block.base = ([^]byte)(uintptr(block) + base_offset)
|
block.base = ([^]byte)(uintptr(block) + base_offset)
|
||||||
block.capacity = uint(end - uintptr(block.base))
|
block.capacity = uint(end - uintptr(block.base))
|
||||||
|
|
||||||
|
sanitizer.address_poison(block.base, block.capacity)
|
||||||
|
|
||||||
// Should be zeroed
|
// Should be zeroed
|
||||||
assert(block.used == 0)
|
assert(block.used == 0)
|
||||||
assert(block.prev == nil)
|
assert(block.prev == nil)
|
||||||
@@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
|
|||||||
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
|
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
|
||||||
if block_to_free != nil {
|
if block_to_free != nil {
|
||||||
allocator := block_to_free.allocator
|
allocator := block_to_free.allocator
|
||||||
|
sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
|
||||||
mem_free(block_to_free, allocator, loc)
|
mem_free(block_to_free, allocator, loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
data = block.base[block.used+alignment_offset:][:min_size]
|
data = block.base[block.used+alignment_offset:][:min_size]
|
||||||
|
sanitizer.address_unpoison(block.base[block.used:block.used+size])
|
||||||
block.used += size
|
block.used += size
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -162,6 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
|||||||
if arena.curr_block != nil {
|
if arena.curr_block != nil {
|
||||||
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
|
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
|
||||||
arena.curr_block.used = 0
|
arena.curr_block.used = 0
|
||||||
|
sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
|
||||||
}
|
}
|
||||||
arena.total_used = 0
|
arena.total_used = 0
|
||||||
}
|
}
|
||||||
@@ -226,6 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
|||||||
// grow data in-place, adjusting next allocation
|
// grow data in-place, adjusting next allocation
|
||||||
block.used = uint(new_end)
|
block.used = uint(new_end)
|
||||||
data = block.base[start:new_end]
|
data = block.base[start:new_end]
|
||||||
|
sanitizer.address_unpoison(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,6 +306,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|||||||
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
|
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
|
||||||
amount_to_zero := block.used-temp.used
|
amount_to_zero := block.used-temp.used
|
||||||
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
|
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
|
||||||
|
sanitizer.address_poison(block.base[temp.used:block.capacity])
|
||||||
block.used = temp.used
|
block.used = temp.used
|
||||||
arena.total_used -= amount_to_zero
|
arena.total_used -= amount_to_zero
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
import "../sanitizer"
|
||||||
|
|
||||||
foreign import kernel32 "system:Kernel32.lib"
|
foreign import kernel32 "system:Kernel32.lib"
|
||||||
|
|
||||||
@(private="file")
|
@(private="file")
|
||||||
@@ -16,7 +18,10 @@ foreign kernel32 {
|
|||||||
|
|
||||||
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||||
HEAP_ZERO_MEMORY :: 0x00000008
|
HEAP_ZERO_MEMORY :: 0x00000008
|
||||||
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
|
ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
|
||||||
|
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
|
||||||
|
sanitizer.address_unpoison(ptr, size)
|
||||||
|
return ptr
|
||||||
}
|
}
|
||||||
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||||
if new_size == 0 {
|
if new_size == 0 {
|
||||||
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HEAP_ZERO_MEMORY :: 0x00000008
|
HEAP_ZERO_MEMORY :: 0x00000008
|
||||||
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
|
new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
|
||||||
|
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
|
||||||
|
sanitizer.address_unpoison(new_ptr, new_size)
|
||||||
|
return new_ptr
|
||||||
}
|
}
|
||||||
_heap_free :: proc "contextless" (ptr: rawptr) {
|
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||||
if ptr == nil {
|
if ptr == nil {
|
||||||
|
|||||||
@@ -1106,3 +1106,10 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
|
|||||||
dst[j>>3] |= the_bit<<(j&7)
|
dst[j>>3] |= the_bit<<(j&7)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when .Address in ODIN_SANITIZER_FLAGS {
|
||||||
|
foreign {
|
||||||
|
__asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ poison or unpoison memory in the same memory region region simultaneously.
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_poison_slice :: proc "contextless" (region: $T/[]$E) {
|
address_poison_slice :: proc "contextless" (region: $T/[]$E) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
|
__asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
|
||||||
@@ -75,6 +76,7 @@ can poison or unpoison memory in the same memory region region simultaneously.
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
|
address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
|
__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
|
||||||
@@ -90,6 +92,7 @@ two threads can poison or unpoison memory in the same memory region region simul
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_poison_ptr :: proc "contextless" (ptr: ^$T) {
|
address_poison_ptr :: proc "contextless" (ptr: ^$T) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__asan_poison_memory_region(ptr, size_of(T))
|
__asan_poison_memory_region(ptr, size_of(T))
|
||||||
@@ -106,6 +109,7 @@ region simultaneously.
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
|
address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__asan_unpoison_memory_region(ptr, size_of(T))
|
__asan_unpoison_memory_region(ptr, size_of(T))
|
||||||
@@ -121,6 +125,7 @@ poison or unpoison memory in the same memory region region simultaneously.
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
|
address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
assert_contextless(len >= 0)
|
assert_contextless(len >= 0)
|
||||||
@@ -128,6 +133,22 @@ address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Marks the region covering `[ptr, ptr+len)` as unaddressable
|
||||||
|
|
||||||
|
Code instrumented with `-sanitize:address` is forbidden from accessing any address
|
||||||
|
within the region. This procedure is not thread-safe because no two threads can
|
||||||
|
poison or unpoison memory in the same memory region region simultaneously.
|
||||||
|
|
||||||
|
When asan is not enabled this procedure does nothing.
|
||||||
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
|
address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
|
||||||
|
when ASAN_ENABLED {
|
||||||
|
__asan_poison_memory_region(ptr, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Marks the region covering `[ptr, ptr+len)` as addressable
|
Marks the region covering `[ptr, ptr+len)` as addressable
|
||||||
|
|
||||||
@@ -137,6 +158,7 @@ threads can poison or unpoison memory in the same memory region region simultane
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
|
address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
assert_contextless(len >= 0)
|
assert_contextless(len >= 0)
|
||||||
@@ -144,16 +166,34 @@ address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Marks the region covering `[ptr, ptr+len)` as addressable
|
||||||
|
|
||||||
|
Code instrumented with `-sanitize:address` is allowed to access any address
|
||||||
|
within the region again. This procedure is not thread-safe because no two
|
||||||
|
threads can poison or unpoison memory in the same memory region region simultaneously.
|
||||||
|
|
||||||
|
When asan is not enabled this procedure does nothing.
|
||||||
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
|
address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
|
||||||
|
when ASAN_ENABLED {
|
||||||
|
__asan_unpoison_memory_region(ptr, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
address_poison :: proc {
|
address_poison :: proc {
|
||||||
address_poison_slice,
|
address_poison_slice,
|
||||||
address_poison_ptr,
|
address_poison_ptr,
|
||||||
address_poison_rawptr,
|
address_poison_rawptr,
|
||||||
|
address_poison_rawptr_uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
address_unpoison :: proc {
|
address_unpoison :: proc {
|
||||||
address_unpoison_slice,
|
address_unpoison_slice,
|
||||||
address_unpoison_ptr,
|
address_unpoison_ptr,
|
||||||
address_unpoison_rawptr,
|
address_unpoison_rawptr,
|
||||||
|
address_unpoison_rawptr_uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -164,6 +204,7 @@ This can be used for logging and/or debugging purposes.
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
|
address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__sanitizer_set_death_callback(callback)
|
__sanitizer_set_death_callback(callback)
|
||||||
@@ -178,7 +219,8 @@ in an asan error.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
address_region_is_poisoned_slice :: proc "contextless" (region: []$T/$E) -> rawptr {
|
@(no_sanitize_address)
|
||||||
|
address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
|
return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
|
||||||
} else {
|
} else {
|
||||||
@@ -194,6 +236,7 @@ in an asan error.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
|
address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_region_is_poisoned(ptr, size_of(T))
|
return __asan_region_is_poisoned(ptr, size_of(T))
|
||||||
@@ -210,6 +253,7 @@ in an asan error.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
|
address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
assert_contextless(len >= 0)
|
assert_contextless(len >= 0)
|
||||||
@@ -219,10 +263,29 @@ address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
|
||||||
|
|
||||||
|
If it is poisoned this procedure returns the address which would result
|
||||||
|
in an asan error.
|
||||||
|
|
||||||
|
When asan is not enabled this procedure returns `nil`.
|
||||||
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
|
address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr {
|
||||||
|
when ASAN_ENABLED {
|
||||||
|
return __asan_region_is_poisoned(region, len)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
address_region_is_poisoned :: proc {
|
address_region_is_poisoned :: proc {
|
||||||
address_region_is_poisoned_slice,
|
address_region_is_poisoned_slice,
|
||||||
address_region_is_poisoned_ptr,
|
address_region_is_poisoned_ptr,
|
||||||
address_region_is_poisoned_rawptr,
|
address_region_is_poisoned_rawptr,
|
||||||
|
address_region_is_poisoned_rawptr_uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -233,6 +296,7 @@ If it is poisoned this procedure returns `true`, otherwise it returns
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `false`.
|
When asan is not enabled this procedure returns `false`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
|
address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_address_is_poisoned(address) != 0
|
return __asan_address_is_poisoned(address) != 0
|
||||||
@@ -248,6 +312,7 @@ This procedure prints the description out to `stdout`.
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_describe_address :: proc "contextless" (address: rawptr) {
|
address_describe_address :: proc "contextless" (address: rawptr) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__asan_describe_address(address)
|
__asan_describe_address(address)
|
||||||
@@ -260,6 +325,7 @@ Returns `true` if an asan error has occured, otherwise it returns
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `false`.
|
When asan is not enabled this procedure returns `false`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_report_present :: proc "contextless" () -> bool {
|
address_report_present :: proc "contextless" () -> bool {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_report_present() != 0
|
return __asan_report_present() != 0
|
||||||
@@ -275,6 +341,7 @@ If no asan error has occurd `nil` is returned.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_report_pc :: proc "contextless" () -> rawptr {
|
address_get_report_pc :: proc "contextless" () -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_get_report_pc()
|
return __asan_get_report_pc()
|
||||||
@@ -290,6 +357,7 @@ If no asan error has occurd `nil` is returned.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_report_bp :: proc "contextless" () -> rawptr {
|
address_get_report_bp :: proc "contextless" () -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_get_report_bp()
|
return __asan_get_report_bp()
|
||||||
@@ -305,6 +373,7 @@ If no asan error has occurd `nil` is returned.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_report_sp :: proc "contextless" () -> rawptr {
|
address_get_report_sp :: proc "contextless" () -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_get_report_sp()
|
return __asan_get_report_sp()
|
||||||
@@ -320,6 +389,7 @@ If no asan error has occurd `nil` is returned.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_report_address :: proc "contextless" () -> rawptr {
|
address_get_report_address :: proc "contextless" () -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_get_report_address()
|
return __asan_get_report_address()
|
||||||
@@ -335,6 +405,7 @@ If no asan error has occurd `.none` is returned.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `.none`.
|
When asan is not enabled this procedure returns `.none`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
|
address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
if ! address_report_present() {
|
if ! address_report_present() {
|
||||||
@@ -353,6 +424,7 @@ If no asan error has occurd `0` is returned.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `0`.
|
When asan is not enabled this procedure returns `0`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_report_access_size :: proc "contextless" () -> uint {
|
address_get_report_access_size :: proc "contextless" () -> uint {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_get_report_access_size()
|
return __asan_get_report_access_size()
|
||||||
@@ -368,6 +440,7 @@ If no asan error has occurd an empty string is returned.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns an empty string.
|
When asan is not enabled this procedure returns an empty string.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_report_description :: proc "contextless" () -> string {
|
address_get_report_description :: proc "contextless" () -> string {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return string(__asan_get_report_description())
|
return string(__asan_get_report_description())
|
||||||
@@ -386,6 +459,7 @@ The information provided include:
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns zero initialised values.
|
When asan is not enabled this procedure returns zero initialised values.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
|
address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
out_addr: rawptr
|
out_addr: rawptr
|
||||||
@@ -404,6 +478,7 @@ The stack trace is filled into the `data` slice.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns a zero initialised value.
|
When asan is not enabled this procedure returns a zero initialised value.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
|
address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
out_thread: i32
|
out_thread: i32
|
||||||
@@ -421,6 +496,7 @@ The stack trace is filled into the `data` slice.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns zero initialised values.
|
When asan is not enabled this procedure returns zero initialised values.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
|
address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
out_thread: i32
|
out_thread: i32
|
||||||
@@ -436,6 +512,7 @@ Returns the current asan shadow memory mapping.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns a zero initialised value.
|
When asan is not enabled this procedure returns a zero initialised value.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
|
address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
result: Address_Shadow_Mapping
|
result: Address_Shadow_Mapping
|
||||||
@@ -451,6 +528,7 @@ Prints asan statistics to `stderr`
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_print_accumulated_stats :: proc "contextless" () {
|
address_print_accumulated_stats :: proc "contextless" () {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__asan_print_accumulated_stats()
|
__asan_print_accumulated_stats()
|
||||||
@@ -464,6 +542,7 @@ This pointer can be then used for `address_is_in_fake_stack`.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `nil`.
|
When asan is not enabled this procedure returns `nil`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_get_current_fake_stack :: proc "contextless" () -> rawptr {
|
address_get_current_fake_stack :: proc "contextless" () -> rawptr {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_get_current_fake_stack()
|
return __asan_get_current_fake_stack()
|
||||||
@@ -477,6 +556,7 @@ Returns if an address belongs to a given fake stack and if so the region of the
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns zero initialised values.
|
When asan is not enabled this procedure returns zero initialised values.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
|
address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
begin: rawptr
|
begin: rawptr
|
||||||
@@ -496,6 +576,7 @@ i.e. a procedure such as `panic` and `os.exit`.
|
|||||||
|
|
||||||
When asan is not enabled this procedure does nothing.
|
When asan is not enabled this procedure does nothing.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_handle_no_return :: proc "contextless" () {
|
address_handle_no_return :: proc "contextless" () {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
__asan_handle_no_return()
|
__asan_handle_no_return()
|
||||||
@@ -509,6 +590,7 @@ Returns `true` if successful, otherwise it returns `false`.
|
|||||||
|
|
||||||
When asan is not enabled this procedure returns `false`.
|
When asan is not enabled this procedure returns `false`.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
|
address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
|
||||||
when ASAN_ENABLED {
|
when ASAN_ENABLED {
|
||||||
return __asan_update_allocation_context(addr) != 0
|
return __asan_update_allocation_context(addr) != 0
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ related bugs. Typically asan interacts with libc but Odin code can be marked up
|
|||||||
with the asan runtime to extend the memory error detection outside of libc using this package.
|
with the asan runtime to extend the memory error detection outside of libc using this package.
|
||||||
For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
|
For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
|
||||||
|
|
||||||
|
Procedures can be made exempt from asan when marked up with @(no_sanitize_address)
|
||||||
|
|
||||||
## Memory
|
## Memory
|
||||||
|
|
||||||
Enabled with `-sanitize:memory` when building an odin project.
|
Enabled with `-sanitize:memory` when building an odin project.
|
||||||
|
|
||||||
The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
|
The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
|
||||||
use of uninitialized memory. This is not a very common bug in Odin as be default everything is
|
use of uninitialized memory. This is not a very common bug in Odin as by default everything is
|
||||||
set to zero when initialised (ZII).
|
set to zero when initialised (ZII).
|
||||||
For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
|
For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package mem
|
package mem
|
||||||
|
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
|
import "base:sanitizer"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Rollback stack default block size.
|
Rollback stack default block size.
|
||||||
@@ -47,14 +48,14 @@ Rollback_Stack :: struct {
|
|||||||
block_allocator: Allocator,
|
block_allocator: Allocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private="file", require_results)
|
@(private="file", require_results, no_sanitize_address)
|
||||||
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
|
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
|
||||||
start := raw_data(block.buffer)
|
start := raw_data(block.buffer)
|
||||||
end := start[block.offset:]
|
end := start[block.offset:]
|
||||||
return start < ptr && ptr <= end
|
return start < ptr && ptr <= end
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private="file", require_results)
|
@(private="file", require_results, no_sanitize_address)
|
||||||
rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
|
rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
|
||||||
parent: ^Rollback_Stack_Block,
|
parent: ^Rollback_Stack_Block,
|
||||||
block: ^Rollback_Stack_Block,
|
block: ^Rollback_Stack_Block,
|
||||||
@@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
|
|||||||
return nil, nil, nil, .Invalid_Pointer
|
return nil, nil, nil, .Invalid_Pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private="file", require_results)
|
@(private="file", require_results, no_sanitize_address)
|
||||||
rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
|
rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
|
||||||
block: ^Rollback_Stack_Block,
|
block: ^Rollback_Stack_Block,
|
||||||
header: ^Rollback_Stack_Header,
|
header: ^Rollback_Stack_Header,
|
||||||
@@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
|
|||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private="file")
|
@(private="file", no_sanitize_address)
|
||||||
rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
|
rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
|
||||||
header := header
|
header := header
|
||||||
|
|
||||||
for block.offset > 0 && header.is_free {
|
for block.offset > 0 && header.is_free {
|
||||||
block.offset = header.prev_offset
|
block.offset = header.prev_offset
|
||||||
block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
|
block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
|
||||||
@@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
|
|||||||
/*
|
/*
|
||||||
Free memory to a rollback stack allocator.
|
Free memory to a rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(private="file", require_results)
|
@(private="file", require_results, no_sanitize_address)
|
||||||
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
|
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
|
||||||
parent, block, header := rb_find_ptr(stack, ptr) or_return
|
parent, block, header := rb_find_ptr(stack, ptr) or_return
|
||||||
|
|
||||||
if header.is_free {
|
if header.is_free {
|
||||||
return .Invalid_Pointer
|
return .Invalid_Pointer
|
||||||
}
|
}
|
||||||
@@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
|
|||||||
/*
|
/*
|
||||||
Free all memory owned by the rollback stack allocator.
|
Free all memory owned by the rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(private="file")
|
@(private="file", no_sanitize_address)
|
||||||
rb_free_all :: proc(stack: ^Rollback_Stack) {
|
rb_free_all :: proc(stack: ^Rollback_Stack) {
|
||||||
for block := stack.head.next_block; block != nil; /**/ {
|
for block := stack.head.next_block; block != nil; /**/ {
|
||||||
next_block := block.next_block
|
next_block := block.next_block
|
||||||
@@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
|
|||||||
stack.head.next_block = nil
|
stack.head.next_block = nil
|
||||||
stack.head.last_alloc = nil
|
stack.head.last_alloc = nil
|
||||||
stack.head.offset = 0
|
stack.head.offset = 0
|
||||||
|
sanitizer.address_poison(stack.head.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Allocate memory using the rollback stack allocator.
|
Allocate memory using the rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_alloc :: proc(
|
rb_alloc :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
size: int,
|
size: int,
|
||||||
@@ -153,7 +157,7 @@ rb_alloc :: proc(
|
|||||||
/*
|
/*
|
||||||
Allocate memory using the rollback stack allocator.
|
Allocate memory using the rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_alloc_bytes :: proc(
|
rb_alloc_bytes :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
size: int,
|
size: int,
|
||||||
@@ -170,7 +174,7 @@ rb_alloc_bytes :: proc(
|
|||||||
/*
|
/*
|
||||||
Allocate non-initialized memory using the rollback stack allocator.
|
Allocate non-initialized memory using the rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_alloc_non_zeroed :: proc(
|
rb_alloc_non_zeroed :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
size: int,
|
size: int,
|
||||||
@@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc(
|
|||||||
/*
|
/*
|
||||||
Allocate non-initialized memory using the rollback stack allocator.
|
Allocate non-initialized memory using the rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_alloc_bytes_non_zeroed :: proc(
|
rb_alloc_bytes_non_zeroed :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
size: int,
|
size: int,
|
||||||
@@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc(
|
|||||||
assert(size >= 0, "Size must be positive or zero.", loc)
|
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)
|
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
|
||||||
parent: ^Rollback_Stack_Block
|
parent: ^Rollback_Stack_Block
|
||||||
|
|
||||||
for block := stack.head; /**/; block = block.next_block {
|
for block := stack.head; /**/; block = block.next_block {
|
||||||
when !ODIN_DISABLE_ASSERT {
|
when !ODIN_DISABLE_ASSERT {
|
||||||
allocated_new_block: bool
|
allocated_new_block: bool
|
||||||
@@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc(
|
|||||||
// Prevent any further allocations on it.
|
// Prevent any further allocations on it.
|
||||||
block.offset = cast(uintptr)len(block.buffer)
|
block.offset = cast(uintptr)len(block.buffer)
|
||||||
}
|
}
|
||||||
#no_bounds_check return ptr[:size], nil
|
res := ptr[:size]
|
||||||
|
sanitizer.address_unpoison(res)
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
return nil, .Out_Of_Memory
|
return nil, .Out_Of_Memory
|
||||||
}
|
}
|
||||||
@@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc(
|
|||||||
/*
|
/*
|
||||||
Resize an allocation owned by rollback stack allocator.
|
Resize an allocation owned by rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_resize :: proc(
|
rb_resize :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
old_ptr: rawptr,
|
old_ptr: rawptr,
|
||||||
@@ -266,7 +273,7 @@ rb_resize :: proc(
|
|||||||
/*
|
/*
|
||||||
Resize an allocation owned by rollback stack allocator.
|
Resize an allocation owned by rollback stack allocator.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_resize_bytes :: proc(
|
rb_resize_bytes :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
old_memory: []byte,
|
old_memory: []byte,
|
||||||
@@ -289,7 +296,7 @@ rb_resize_bytes :: proc(
|
|||||||
Resize an allocation owned by rollback stack allocator without explicit
|
Resize an allocation owned by rollback stack allocator without explicit
|
||||||
zero-initialization.
|
zero-initialization.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_resize_non_zeroed :: proc(
|
rb_resize_non_zeroed :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
old_ptr: rawptr,
|
old_ptr: rawptr,
|
||||||
@@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc(
|
|||||||
Resize an allocation owned by rollback stack allocator without explicit
|
Resize an allocation owned by rollback stack allocator without explicit
|
||||||
zero-initialization.
|
zero-initialization.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rb_resize_bytes_non_zeroed :: proc(
|
rb_resize_bytes_non_zeroed :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
old_memory: []byte,
|
old_memory: []byte,
|
||||||
@@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc(
|
|||||||
if len(block.buffer) <= stack.block_size {
|
if len(block.buffer) <= stack.block_size {
|
||||||
block.offset += cast(uintptr)size - cast(uintptr)old_size
|
block.offset += cast(uintptr)size - cast(uintptr)old_size
|
||||||
}
|
}
|
||||||
#no_bounds_check return (ptr)[:size], nil
|
res := (ptr)[:size]
|
||||||
|
sanitizer.address_unpoison(res)
|
||||||
|
#no_bounds_check return res, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private="file", require_results)
|
@(private="file", require_results, no_sanitize_address)
|
||||||
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
|
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
|
||||||
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
|
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
|
||||||
block = cast(^Rollback_Stack_Block)raw_data(buffer)
|
block = cast(^Rollback_Stack_Block)raw_data(buffer)
|
||||||
@@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac
|
|||||||
/*
|
/*
|
||||||
Initialize the rollback stack allocator using a fixed backing buffer.
|
Initialize the rollback stack allocator using a fixed backing buffer.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
|
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
|
||||||
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
|
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
|
||||||
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
|
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
|
||||||
@@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc
|
|||||||
/*
|
/*
|
||||||
Initialize the rollback stack alocator using a backing block allocator.
|
Initialize the rollback stack alocator using a backing block allocator.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
rollback_stack_init_dynamic :: proc(
|
rollback_stack_init_dynamic :: proc(
|
||||||
stack: ^Rollback_Stack,
|
stack: ^Rollback_Stack,
|
||||||
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
|
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
|
||||||
@@ -396,6 +407,7 @@ rollback_stack_init :: proc {
|
|||||||
/*
|
/*
|
||||||
Destroy a rollback stack.
|
Destroy a rollback stack.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
|
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
|
||||||
if stack.block_allocator.procedure != nil {
|
if stack.block_allocator.procedure != nil {
|
||||||
rb_free_all(stack)
|
rb_free_all(stack)
|
||||||
@@ -435,7 +447,7 @@ from the last allocation backwards.
|
|||||||
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
|
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
|
||||||
the requested alignment.
|
the requested alignment.
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
|
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
|
||||||
return Allocator {
|
return Allocator {
|
||||||
data = stack,
|
data = stack,
|
||||||
@@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
rollback_stack_allocator_proc :: proc(
|
rollback_stack_allocator_proc :: proc(
|
||||||
allocator_data: rawptr,
|
allocator_data: rawptr,
|
||||||
mode: Allocator_Mode,
|
mode: Allocator_Mode,
|
||||||
|
|||||||
@@ -198,4 +198,4 @@ fls :: proc "contextless" (word: u32) -> (bit: i32) {
|
|||||||
fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
|
fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
|
||||||
N :: (size_of(uint) * 8) - 1
|
N :: (size_of(uint) * 8) - 1
|
||||||
return i32(N - intrinsics.count_leading_zeros(size))
|
return i32(N - intrinsics.count_leading_zeros(size))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
package mem_tlsf
|
package mem_tlsf
|
||||||
|
|
||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
|
import "base:sanitizer"
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
|
|
||||||
// log2 of number of linear subdivisions of block sizes.
|
// log2 of number of linear subdivisions of block sizes.
|
||||||
@@ -209,6 +210,8 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
|
|||||||
return nil, .Out_Of_Memory
|
return nil, .Out_Of_Memory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizer.address_poison(new_pool_buf)
|
||||||
|
|
||||||
// Allocate a new link in the `control.pool` tracking structure.
|
// Allocate a new link in the `control.pool` tracking structure.
|
||||||
new_pool := new_clone(Pool{
|
new_pool := new_clone(Pool{
|
||||||
data = new_pool_buf,
|
data = new_pool_buf,
|
||||||
@@ -254,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
|
|||||||
return block_prepare_used(control, block, adjust)
|
return block_prepare_used(control, block, adjust)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
|
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
|
||||||
res, err = alloc_bytes_non_zeroed(control, size, align)
|
res, err = alloc_bytes_non_zeroed(control, size, align)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -273,6 +276,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
|
|||||||
|
|
||||||
block := block_from_ptr(ptr)
|
block := block_from_ptr(ptr)
|
||||||
assert(!block_is_free(block), "block already marked as free") // double free
|
assert(!block_is_free(block), "block already marked as free") // double free
|
||||||
|
sanitizer.address_poison(ptr, block.size)
|
||||||
block_mark_as_free(block)
|
block_mark_as_free(block)
|
||||||
block = block_merge_prev(control, block)
|
block = block_merge_prev(control, block)
|
||||||
block = block_merge_next(control, block)
|
block = block_merge_next(control, block)
|
||||||
@@ -316,6 +320,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
|
|||||||
|
|
||||||
block_trim_used(control, block, adjust)
|
block_trim_used(control, block, adjust)
|
||||||
res = ([^]byte)(ptr)[:new_size]
|
res = ([^]byte)(ptr)[:new_size]
|
||||||
|
sanitizer.address_unpoison(res)
|
||||||
|
|
||||||
if min_size < new_size {
|
if min_size < new_size {
|
||||||
to_zero := ([^]byte)(ptr)[min_size:new_size]
|
to_zero := ([^]byte)(ptr)[min_size:new_size]
|
||||||
@@ -374,95 +379,96 @@ resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size:
|
|||||||
NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31.
|
NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
|
block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
|
||||||
return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
|
return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
|
block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
|
||||||
old_size := block.size
|
old_size := block.size
|
||||||
block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
|
block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
|
block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
|
||||||
return block_size(block) == 0
|
return block_size(block) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
|
block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
|
||||||
return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
|
return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_set_free :: proc "contextless" (block: ^Block_Header) {
|
block_set_free :: proc "contextless" (block: ^Block_Header) {
|
||||||
block.size |= BLOCK_HEADER_FREE
|
block.size |= BLOCK_HEADER_FREE
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_set_used :: proc "contextless" (block: ^Block_Header) {
|
block_set_used :: proc "contextless" (block: ^Block_Header) {
|
||||||
block.size &~= BLOCK_HEADER_FREE
|
block.size &~= BLOCK_HEADER_FREE
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
|
block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
|
||||||
return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
|
return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
|
block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
|
||||||
block.size |= BLOCK_HEADER_PREV_FREE
|
block.size |= BLOCK_HEADER_PREV_FREE
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
|
block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
|
||||||
block.size &~= BLOCK_HEADER_PREV_FREE
|
block.size &~= BLOCK_HEADER_PREV_FREE
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
|
block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
|
||||||
return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
|
return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) {
|
block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) {
|
||||||
return rawptr(uintptr(block) + BLOCK_START_OFFSET)
|
return rawptr(uintptr(block) + BLOCK_START_OFFSET)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return location of next block after block of given size.
|
// Return location of next block after block of given size.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
|
offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
|
||||||
return (^Block_Header)(uintptr(ptr) + uintptr(size))
|
return (^Block_Header)(uintptr(ptr) + uintptr(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
|
offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
|
||||||
return (^Block_Header)(uintptr(ptr) - uintptr(size))
|
return (^Block_Header)(uintptr(ptr) - uintptr(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return location of previous block.
|
// Return location of previous block.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
|
block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
|
||||||
assert(block_is_prev_free(block), "previous block must be free")
|
assert(block_is_prev_free(block), "previous block must be free")
|
||||||
|
|
||||||
return block.prev_phys_block
|
return block.prev_phys_block
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return location of next existing block.
|
// Return location of next existing block.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
|
block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
|
||||||
return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
|
return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link a new block with its physical neighbor, return the neighbor.
|
// Link a new block with its physical neighbor, return the neighbor.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
|
block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
|
||||||
next = block_next(block)
|
next = block_next(block)
|
||||||
next.prev_phys_block = block
|
next.prev_phys_block = block
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_mark_as_free :: proc(block: ^Block_Header) {
|
block_mark_as_free :: proc(block: ^Block_Header) {
|
||||||
// Link the block to the next block, first.
|
// Link the block to the next block, first.
|
||||||
next := block_link_next(block)
|
next := block_link_next(block)
|
||||||
@@ -470,26 +476,26 @@ block_mark_as_free :: proc(block: ^Block_Header) {
|
|||||||
block_set_free(block)
|
block_set_free(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_mark_as_used :: proc(block: ^Block_Header) {
|
block_mark_as_used :: proc(block: ^Block_Header, ) {
|
||||||
next := block_next(block)
|
next := block_next(block)
|
||||||
block_set_prev_used(next)
|
block_set_prev_used(next)
|
||||||
block_set_used(block)
|
block_set_used(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
align_up :: proc(x, align: uint) -> (aligned: uint) {
|
align_up :: proc(x, align: uint) -> (aligned: uint) {
|
||||||
assert(0 == (align & (align - 1)), "must align to a power of two")
|
assert(0 == (align & (align - 1)), "must align to a power of two")
|
||||||
return (x + (align - 1)) &~ (align - 1)
|
return (x + (align - 1)) &~ (align - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
align_down :: proc(x, align: uint) -> (aligned: uint) {
|
align_down :: proc(x, align: uint) -> (aligned: uint) {
|
||||||
assert(0 == (align & (align - 1)), "must align to a power of two")
|
assert(0 == (align & (align - 1)), "must align to a power of two")
|
||||||
return x - (x & (align - 1))
|
return x - (x & (align - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
|
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
|
||||||
assert(0 == (align & (align - 1)), "must align to a power of two")
|
assert(0 == (align & (align - 1)), "must align to a power of two")
|
||||||
align_mask := uintptr(align) - 1
|
align_mask := uintptr(align) - 1
|
||||||
@@ -499,7 +505,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
|
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
|
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return 0
|
return 0
|
||||||
@@ -513,7 +519,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
|
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
|
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
@@ -531,7 +537,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err:
|
|||||||
// TLSF utility functions. In most cases these are direct translations of
|
// TLSF utility functions. In most cases these are direct translations of
|
||||||
// the documentation in the research paper.
|
// the documentation in the research paper.
|
||||||
|
|
||||||
@(optimization_mode="favor_size", private, require_results)
|
@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
|
||||||
mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
|
mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
|
||||||
if size < SMALL_BLOCK_SIZE {
|
if size < SMALL_BLOCK_SIZE {
|
||||||
// Store small blocks in first list.
|
// Store small blocks in first list.
|
||||||
@@ -544,7 +550,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@(optimization_mode="favor_size", private, require_results)
|
@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
|
||||||
mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
|
mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
|
||||||
rounded = size
|
rounded = size
|
||||||
if size >= SMALL_BLOCK_SIZE {
|
if size >= SMALL_BLOCK_SIZE {
|
||||||
@@ -555,12 +561,12 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This version rounds up to the next block size (for allocations)
|
// This version rounds up to the next block size (for allocations)
|
||||||
@(optimization_mode="favor_size", private, require_results)
|
@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
|
||||||
mapping_search :: proc(size: uint) -> (fl, sl: i32) {
|
mapping_search :: proc(size: uint) -> (fl, sl: i32) {
|
||||||
return mapping_insert(mapping_round(size))
|
return mapping_insert(mapping_round(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
|
search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
|
||||||
// First, search for a block in the list associated with the given fl/sl index.
|
// First, search for a block in the list associated with the given fl/sl index.
|
||||||
fl := fli^; sl := sli^
|
fl := fli^; sl := sli^
|
||||||
@@ -587,7 +593,7 @@ search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^B
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove a free block from the free list.
|
// Remove a free block from the free list.
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
|
remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
|
||||||
prev := block.prev_free
|
prev := block.prev_free
|
||||||
next := block.next_free
|
next := block.next_free
|
||||||
@@ -613,7 +619,7 @@ remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Insert a free block into the free block list.
|
// Insert a free block into the free block list.
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
|
insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
|
||||||
current := control.blocks[fl][sl]
|
current := control.blocks[fl][sl]
|
||||||
assert(current != nil, "free lists cannot have a nil entry")
|
assert(current != nil, "free lists cannot have a nil entry")
|
||||||
@@ -631,26 +637,26 @@ insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove a given block from the free list.
|
// Remove a given block from the free list.
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
|
block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
|
||||||
fl, sl := mapping_insert(block_size(block))
|
fl, sl := mapping_insert(block_size(block))
|
||||||
remove_free_block(control, block, fl, sl)
|
remove_free_block(control, block, fl, sl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a given block into the free list.
|
// Insert a given block into the free list.
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
|
block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
|
||||||
fl, sl := mapping_insert(block_size(block))
|
fl, sl := mapping_insert(block_size(block))
|
||||||
insert_free_block(control, block, fl, sl)
|
insert_free_block(control, block, fl, sl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
|
block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
|
||||||
return block_size(block) >= size_of(Block_Header) + size
|
return block_size(block) >= size_of(Block_Header) + size
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split a block into two, the second of which is free.
|
// Split a block into two, the second of which is free.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
|
block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
|
||||||
// Calculate the amount of space left in the remaining block.
|
// Calculate the amount of space left in the remaining block.
|
||||||
remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
|
remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
|
||||||
@@ -671,9 +677,10 @@ block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Head
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Absorb a free block's storage into an adjacent previous free block.
|
// Absorb a free block's storage into an adjacent previous free block.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
|
block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
|
||||||
assert(!block_is_last(prev), "previous block can't be last")
|
assert(!block_is_last(prev), "previous block can't be last")
|
||||||
|
|
||||||
// Note: Leaves flags untouched.
|
// Note: Leaves flags untouched.
|
||||||
prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
|
prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
|
||||||
_ = block_link_next(prev)
|
_ = block_link_next(prev)
|
||||||
@@ -681,7 +688,7 @@ block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^B
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge a just-freed block with an adjacent previous free block.
|
// Merge a just-freed block with an adjacent previous free block.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
|
block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
|
||||||
merged = block
|
merged = block
|
||||||
if (block_is_prev_free(block)) {
|
if (block_is_prev_free(block)) {
|
||||||
@@ -695,7 +702,7 @@ block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge a just-freed block with an adjacent free block.
|
// Merge a just-freed block with an adjacent free block.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
|
block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
|
||||||
merged = block
|
merged = block
|
||||||
next := block_next(block)
|
next := block_next(block)
|
||||||
@@ -710,7 +717,7 @@ block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trim any trailing block space off the end of a free block, return to pool.
|
// Trim any trailing block space off the end of a free block, return to pool.
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
|
block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
|
||||||
assert(block_is_free(block), "block must be free")
|
assert(block_is_free(block), "block must be free")
|
||||||
if (block_can_split(block, size)) {
|
if (block_can_split(block, size)) {
|
||||||
@@ -722,7 +729,7 @@ block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trim any trailing block space off the end of a used block, return to pool.
|
// Trim any trailing block space off the end of a used block, return to pool.
|
||||||
@(private)
|
@(private, no_sanitize_address)
|
||||||
block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
|
block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
|
||||||
assert(!block_is_free(block), "Block must be used")
|
assert(!block_is_free(block), "Block must be used")
|
||||||
if (block_can_split(block, size)) {
|
if (block_can_split(block, size)) {
|
||||||
@@ -736,7 +743,7 @@ block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trim leading block space, return to pool.
|
// Trim leading block space, return to pool.
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
|
block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
|
||||||
remaining = block
|
remaining = block
|
||||||
if block_can_split(block, size) {
|
if block_can_split(block, size) {
|
||||||
@@ -750,7 +757,7 @@ block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size:
|
|||||||
return remaining
|
return remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
|
block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
|
||||||
fl, sl: i32
|
fl, sl: i32
|
||||||
if size != 0 {
|
if size != 0 {
|
||||||
@@ -774,13 +781,14 @@ block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Hea
|
|||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private, require_results)
|
@(private, require_results, no_sanitize_address)
|
||||||
block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
|
block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
|
||||||
if block != nil {
|
if block != nil {
|
||||||
assert(size != 0, "Size must be non-zero")
|
assert(size != 0, "Size must be non-zero")
|
||||||
block_trim_free(control, block, size)
|
block_trim_free(control, block, size)
|
||||||
block_mark_as_used(block)
|
block_mark_as_used(block)
|
||||||
res = ([^]byte)(block_to_ptr(block))[:size]
|
res = ([^]byte)(block_to_ptr(block))[:size]
|
||||||
|
sanitizer.address_unpoison(res)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator
|
|||||||
specified with `backing_allocator`. The `internals_allocator` will used to
|
specified with `backing_allocator`. The `internals_allocator` will used to
|
||||||
allocate the tracked data.
|
allocate the tracked data.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
|
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
|
||||||
t.backing = backing_allocator
|
t.backing = backing_allocator
|
||||||
t.allocation_map.allocator = internals_allocator
|
t.allocation_map.allocator = internals_allocator
|
||||||
@@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
|
|||||||
/*
|
/*
|
||||||
Destroy the tracking allocator.
|
Destroy the tracking allocator.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
||||||
delete(t.allocation_map)
|
delete(t.allocation_map)
|
||||||
delete(t.bad_free_array)
|
delete(t.bad_free_array)
|
||||||
@@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator.
|
|||||||
**Note**: This procedure clears only the current allocation data while keeping
|
**Note**: This procedure clears only the current allocation data while keeping
|
||||||
the totals intact.
|
the totals intact.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
||||||
sync.mutex_lock(&t.mutex)
|
sync.mutex_lock(&t.mutex)
|
||||||
clear(&t.allocation_map)
|
clear(&t.allocation_map)
|
||||||
@@ -103,6 +106,7 @@ Reset the tracking allocator.
|
|||||||
|
|
||||||
Reset all of a Tracking Allocator's allocation data back to zero.
|
Reset all of a Tracking Allocator's allocation data back to zero.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
|
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
|
||||||
sync.mutex_lock(&t.mutex)
|
sync.mutex_lock(&t.mutex)
|
||||||
clear(&t.allocation_map)
|
clear(&t.allocation_map)
|
||||||
@@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For
|
|||||||
example, you can use tracking_allocator_bad_free_callback_add_to_array to return
|
example, you can use tracking_allocator_bad_free_callback_add_to_array to return
|
||||||
the tracking allocator to the old behavior, where the bad_free_array was used.
|
the tracking allocator to the old behavior, where the bad_free_array was used.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
|
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
|
||||||
runtime.print_caller_location(location)
|
runtime.print_caller_location(location)
|
||||||
runtime.print_string(" Tracking allocator error: Bad free of pointer ")
|
runtime.print_string(" Tracking allocator error: Bad free of pointer ")
|
||||||
@@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor
|
|||||||
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
|
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
|
||||||
then you must make sure to check Tracking_Allocator.bad_free_array at some point.
|
then you must make sure to check Tracking_Allocator.bad_free_array at some point.
|
||||||
*/
|
*/
|
||||||
|
@(no_sanitize_address)
|
||||||
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
|
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
|
||||||
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
|
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
|
||||||
memory = memory,
|
memory = memory,
|
||||||
@@ -175,7 +181,7 @@ Example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
||||||
return Allocator{
|
return Allocator{
|
||||||
data = data,
|
data = data,
|
||||||
@@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
tracking_allocator_proc :: proc(
|
tracking_allocator_proc :: proc(
|
||||||
allocator_data: rawptr,
|
allocator_data: rawptr,
|
||||||
mode: Allocator_Mode,
|
mode: Allocator_Mode,
|
||||||
@@ -191,6 +198,7 @@ tracking_allocator_proc :: proc(
|
|||||||
old_size: int,
|
old_size: int,
|
||||||
loc := #caller_location,
|
loc := #caller_location,
|
||||||
) -> (result: []byte, err: Allocator_Error) {
|
) -> (result: []byte, err: Allocator_Error) {
|
||||||
|
@(no_sanitize_address)
|
||||||
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
|
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
|
||||||
data.total_memory_allocated += i64(entry.size)
|
data.total_memory_allocated += i64(entry.size)
|
||||||
data.total_allocation_count += 1
|
data.total_allocation_count += 1
|
||||||
@@ -200,6 +208,7 @@ tracking_allocator_proc :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
|
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
|
||||||
data.total_memory_freed += i64(entry.size)
|
data.total_memory_freed += i64(entry.size)
|
||||||
data.total_free_count += 1
|
data.total_free_count += 1
|
||||||
|
|||||||
+32
-11
@@ -3,6 +3,8 @@ package mem_virtual
|
|||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:sync"
|
import "core:sync"
|
||||||
|
|
||||||
|
import "base:sanitizer"
|
||||||
|
|
||||||
Arena_Kind :: enum uint {
|
Arena_Kind :: enum uint {
|
||||||
Growing = 0, // Chained memory blocks (singly linked list).
|
Growing = 0, // Chained memory blocks (singly linked list).
|
||||||
Static = 1, // Fixed reservation sized.
|
Static = 1, // Fixed reservation sized.
|
||||||
@@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
|
|||||||
|
|
||||||
// Initialization of an `Arena` to be a `.Growing` variant.
|
// Initialization of an `Arena` to be a `.Growing` variant.
|
||||||
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
|
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
|
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
|
||||||
arena.kind = .Growing
|
arena.kind = .Growing
|
||||||
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
|
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
|
||||||
@@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
|
|||||||
if arena.minimum_block_size == 0 {
|
if arena.minimum_block_size == 0 {
|
||||||
arena.minimum_block_size = reserved
|
arena.minimum_block_size = reserved
|
||||||
}
|
}
|
||||||
|
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Initialization of an `Arena` to be a `.Static` variant.
|
// Initialization of an `Arena` to be a `.Static` variant.
|
||||||
// A static arena contains a single `Memory_Block` allocated with virtual memory.
|
// A static arena contains a single `Memory_Block` allocated with virtual memory.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
|
arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
|
||||||
arena.kind = .Static
|
arena.kind = .Static
|
||||||
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
|
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
|
||||||
arena.total_used = 0
|
arena.total_used = 0
|
||||||
arena.total_reserved = arena.curr_block.reserved
|
arena.total_reserved = arena.curr_block.reserved
|
||||||
|
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialization of an `Arena` to be a `.Buffer` variant.
|
// Initialization of an `Arena` to be a `.Buffer` variant.
|
||||||
// A buffer arena contains single `Memory_Block` created from a user provided []byte.
|
// A buffer arena contains single `Memory_Block` created from a user provided []byte.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
|
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
|
||||||
if len(buffer) < size_of(Memory_Block) {
|
if len(buffer) < size_of(Memory_Block) {
|
||||||
return .Out_Of_Memory
|
return .Out_Of_Memory
|
||||||
@@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
|
|||||||
|
|
||||||
arena.kind = .Buffer
|
arena.kind = .Buffer
|
||||||
|
|
||||||
mem.zero_slice(buffer)
|
sanitizer.address_poison(buffer[:])
|
||||||
|
|
||||||
block_base := raw_data(buffer)
|
block_base := raw_data(buffer)
|
||||||
block := (^Memory_Block)(block_base)
|
block := (^Memory_Block)(block_base)
|
||||||
@@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allocates memory from the provided arena.
|
// Allocates memory from the provided arena.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
||||||
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
||||||
|
|
||||||
@@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
|||||||
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
|
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
|
||||||
arena.total_used = arena.curr_block.used
|
arena.total_used = arena.curr_block.used
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizer.address_unpoison(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
|
// Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
|
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
|
||||||
sync.mutex_guard(&arena.mutex)
|
sync.mutex_guard(&arena.mutex)
|
||||||
|
|
||||||
@@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
|
|||||||
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
|
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
|
||||||
}
|
}
|
||||||
arena.total_used = arena.curr_block.used
|
arena.total_used = arena.curr_block.used
|
||||||
|
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
|
||||||
return true
|
return true
|
||||||
} else if pos == 0 {
|
} else if pos == 0 {
|
||||||
arena.total_used = 0
|
arena.total_used = 0
|
||||||
@@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Frees the last memory block of a Growing Arena
|
// Frees the last memory block of a Growing Arena
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
if free_block := arena.curr_block; free_block != nil {
|
if free_block := arena.curr_block; free_block != nil {
|
||||||
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
|
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
|
||||||
@@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
|
|||||||
arena.total_reserved -= free_block.reserved
|
arena.total_reserved -= free_block.reserved
|
||||||
|
|
||||||
arena.curr_block = free_block.prev
|
arena.curr_block = free_block.prev
|
||||||
|
sanitizer.address_poison(free_block.base[:free_block.committed])
|
||||||
memory_block_dealloc(free_block)
|
memory_block_dealloc(free_block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
|
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
switch arena.kind {
|
switch arena.kind {
|
||||||
case .Growing:
|
case .Growing:
|
||||||
@@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
|||||||
if arena.curr_block != nil {
|
if arena.curr_block != nil {
|
||||||
curr_block_used := int(arena.curr_block.used)
|
curr_block_used := int(arena.curr_block.used)
|
||||||
arena.curr_block.used = 0
|
arena.curr_block.used = 0
|
||||||
|
sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
|
||||||
mem.zero(arena.curr_block.base, curr_block_used)
|
mem.zero(arena.curr_block.base, curr_block_used)
|
||||||
|
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
|
||||||
}
|
}
|
||||||
arena.total_used = 0
|
arena.total_used = 0
|
||||||
case .Static, .Buffer:
|
case .Static, .Buffer:
|
||||||
@@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
|||||||
|
|
||||||
// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
|
// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
|
||||||
// A buffer based arena does not `delete` the provided `[]byte` bufffer.
|
// A buffer based arena does not `delete` the provided `[]byte` bufffer.
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
sync.mutex_guard(&arena.mutex)
|
sync.mutex_guard(&arena.mutex)
|
||||||
switch arena.kind {
|
switch arena.kind {
|
||||||
@@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
bootstrap: Arena
|
bootstrap: Arena
|
||||||
bootstrap.kind = .Growing
|
bootstrap.kind = .Growing
|
||||||
@@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
|
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
bootstrap: Arena
|
bootstrap: Arena
|
||||||
bootstrap.kind = .Static
|
bootstrap.kind = .Static
|
||||||
@@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
|
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create an `Allocator` from the provided `Arena`
|
// Create an `Allocator` from the provided `Arena`
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
|
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
|
||||||
return mem.Allocator{arena_allocator_proc, arena}
|
return mem.Allocator{arena_allocator_proc, arena}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The allocator procedure used by an `Allocator` produced by `arena_allocator`
|
// The allocator procedure used by an `Allocator` produced by `arena_allocator`
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||||
size, alignment: int,
|
size, alignment: int,
|
||||||
old_memory: rawptr, old_size: int,
|
old_memory: rawptr, old_size: int,
|
||||||
@@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|||||||
if size < old_size {
|
if size < old_size {
|
||||||
// shrink data in-place
|
// shrink data in-place
|
||||||
data = old_data[:size]
|
data = old_data[:size]
|
||||||
|
sanitizer.address_poison(old_data[size:old_size])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|||||||
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
|
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
|
||||||
arena.total_used += block.used - prev_used
|
arena.total_used += block.used - prev_used
|
||||||
data = block.base[start:new_end]
|
data = block.base[start:new_end]
|
||||||
|
sanitizer.address_unpoison(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
copy(new_memory, old_data[:old_size])
|
copy(new_memory, old_data[:old_size])
|
||||||
|
sanitizer.address_poison(old_data[:old_size])
|
||||||
return new_memory, nil
|
return new_memory, nil
|
||||||
case .Query_Features:
|
case .Query_Features:
|
||||||
set := (^mem.Allocator_Mode_Set)(old_memory)
|
set := (^mem.Allocator_Mode_Set)(old_memory)
|
||||||
@@ -382,7 +400,7 @@ Arena_Temp :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Begins the section of temporary arena memory.
|
// Begins the section of temporary arena memory.
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
||||||
assert(arena != nil, "nil arena", loc)
|
assert(arena != nil, "nil arena", loc)
|
||||||
sync.mutex_guard(&arena.mutex)
|
sync.mutex_guard(&arena.mutex)
|
||||||
@@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ends the section of temporary arena memory by resetting the memory to the stored position.
|
// Ends the section of temporary arena memory by resetting the memory to the stored position.
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
assert(temp.arena != nil, "nil arena", loc)
|
assert(temp.arena != nil, "nil arena", loc)
|
||||||
arena := temp.arena
|
arena := temp.arena
|
||||||
@@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
|
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
assert(temp.arena != nil, "nil arena", loc)
|
assert(temp.arena != nil, "nil arena", loc)
|
||||||
arena := temp.arena
|
arena := temp.arena
|
||||||
@@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
|
// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
|
||||||
|
@(no_sanitize_address)
|
||||||
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package mem_virtual
|
|||||||
|
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
|
import "base:sanitizer"
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
_ :: runtime
|
_ :: runtime
|
||||||
|
|
||||||
@@ -14,27 +15,33 @@ platform_memory_init :: proc() {
|
|||||||
|
|
||||||
Allocator_Error :: mem.Allocator_Error
|
Allocator_Error :: mem.Allocator_Error
|
||||||
|
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
return _reserve(size)
|
return _reserve(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
||||||
|
sanitizer.address_unpoison(data, size)
|
||||||
return _commit(data, size)
|
return _commit(data, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
data = reserve(size) or_return
|
data = reserve(size) or_return
|
||||||
commit(raw_data(data), size) or_return
|
commit(raw_data(data), size) or_return
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
decommit :: proc "contextless" (data: rawptr, size: uint) {
|
decommit :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
|
sanitizer.address_poison(data, size)
|
||||||
_decommit(data, size)
|
_decommit(data, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
release :: proc "contextless" (data: rawptr, size: uint) {
|
release :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
|
sanitizer.address_unpoison(data, size)
|
||||||
_release(data, size)
|
_release(data, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,13 +53,11 @@ Protect_Flag :: enum u32 {
|
|||||||
Protect_Flags :: distinct bit_set[Protect_Flag; u32]
|
Protect_Flags :: distinct bit_set[Protect_Flag; u32]
|
||||||
Protect_No_Access :: Protect_Flags{}
|
Protect_No_Access :: Protect_Flags{}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
|
protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
|
||||||
return _protect(data, size, flags)
|
return _protect(data, size, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Memory_Block :: struct {
|
Memory_Block :: struct {
|
||||||
prev: ^Memory_Block,
|
prev: ^Memory_Block,
|
||||||
base: [^]byte,
|
base: [^]byte,
|
||||||
@@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 {
|
|||||||
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
|
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
|
||||||
|
|
||||||
|
|
||||||
@(private="file", require_results)
|
@(private="file", require_results, no_sanitize_address)
|
||||||
align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
|
align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
|
||||||
result := size + align-1
|
result := size + align-1
|
||||||
return result - result%align
|
return result - result%align
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
|
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
|
||||||
page_size := DEFAULT_PAGE_SIZE
|
page_size := DEFAULT_PAGE_SIZE
|
||||||
assert(mem.is_power_of_two(uintptr(page_size)))
|
assert(mem.is_power_of_two(uintptr(page_size)))
|
||||||
@@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
|
|||||||
return &pmblock.block, nil
|
return &pmblock.block, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results, no_sanitize_address)
|
||||||
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
|
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
@(no_sanitize_address)
|
||||||
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
|
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
|
||||||
alignment_offset := uint(0)
|
alignment_offset := uint(0)
|
||||||
ptr := uintptr(block.base[block.used:])
|
ptr := uintptr(block.base[block.used:])
|
||||||
@@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
|
|||||||
return alignment_offset
|
return alignment_offset
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@(no_sanitize_address)
|
||||||
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
|
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
|
||||||
if block.committed - block.used < size {
|
if block.committed - block.used < size {
|
||||||
pmblock := (^Platform_Memory_Block)(block)
|
pmblock := (^Platform_Memory_Block)(block)
|
||||||
@@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
|
|||||||
|
|
||||||
data = block.base[block.used+alignment_offset:][:min_size]
|
data = block.base[block.used+alignment_offset:][:min_size]
|
||||||
block.used += size
|
block.used += size
|
||||||
|
sanitizer.address_unpoison(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
|
memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
|
||||||
if block := (^Platform_Memory_Block)(block_to_free); block != nil {
|
if block := (^Platform_Memory_Block)(block_to_free); block != nil {
|
||||||
platform_memory_free(block)
|
platform_memory_free(block)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Platform_Memory_Block :: struct {
|
|||||||
reserved: uint,
|
reserved: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
|
platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
|
||||||
to_commit, to_reserve := to_commit, to_reserve
|
to_commit, to_reserve := to_commit, to_reserve
|
||||||
to_reserve = max(to_commit, to_reserve)
|
to_reserve = max(to_commit, to_reserve)
|
||||||
@@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
|
platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
|
||||||
if block != nil {
|
if block != nil {
|
||||||
release(block, block.reserved)
|
release(block, block.reserved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
|
platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
|
||||||
if to_commit < block.committed {
|
if to_commit < block.committed {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ foreign Kernel32 {
|
|||||||
dwNumberOfBytesToMap: uint,
|
dwNumberOfBytesToMap: uint,
|
||||||
) -> rawptr ---
|
) -> rawptr ---
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
|
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
@@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
||||||
result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
|
result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
@@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
_decommit :: proc "contextless" (data: rawptr, size: uint) {
|
_decommit :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
VirtualFree(data, size, MEM_DECOMMIT)
|
VirtualFree(data, size, MEM_DECOMMIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
_release :: proc "contextless" (data: rawptr, size: uint) {
|
_release :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
VirtualFree(data, 0, MEM_RELEASE)
|
VirtualFree(data, 0, MEM_RELEASE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
|
_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
|
||||||
pflags: u32
|
pflags: u32
|
||||||
pflags = PAGE_NOACCESS
|
pflags = PAGE_NOACCESS
|
||||||
@@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
_platform_memory_init :: proc() {
|
_platform_memory_init :: proc() {
|
||||||
sys_info: SYSTEM_INFO
|
sys_info: SYSTEM_INFO
|
||||||
GetSystemInfo(&sys_info)
|
GetSystemInfo(&sys_info)
|
||||||
@@ -147,6 +156,7 @@ _platform_memory_init :: proc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@(no_sanitize_address)
|
||||||
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||||
page_flags: u32
|
page_flags: u32
|
||||||
if flags == {.Read} {
|
if flags == {.Read} {
|
||||||
|
|||||||
@@ -383,6 +383,8 @@ struct lbProcedure {
|
|||||||
PtrMap<Ast *, lbValue> selector_values;
|
PtrMap<Ast *, lbValue> selector_values;
|
||||||
PtrMap<Ast *, lbAddr> selector_addr;
|
PtrMap<Ast *, lbAddr> selector_addr;
|
||||||
PtrMap<LLVMValueRef, lbTupleFix> tuple_fix_map;
|
PtrMap<LLVMValueRef, lbTupleFix> tuple_fix_map;
|
||||||
|
|
||||||
|
Array<lbValue> asan_stack_locals;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3070,6 +3070,13 @@ gb_internal lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero
|
|||||||
if (e != nullptr) {
|
if (e != nullptr) {
|
||||||
lb_add_entity(p->module, e, val);
|
lb_add_entity(p->module, e, val);
|
||||||
lb_add_debug_local_variable(p, ptr, type, e->token);
|
lb_add_debug_local_variable(p, ptr, type, e->token);
|
||||||
|
|
||||||
|
// NOTE(lucas): In LLVM 20 and below we do not have the option to have asan cleanup poisoned stack
|
||||||
|
// locals ourselves. So we need to manually track and unpoison these locals on proc return.
|
||||||
|
// LLVM 21 adds the 'use-after-scope' asan option which does this for us.
|
||||||
|
if (build_context.sanitizer_flags & SanitizerFlag_Address && !p->entity->Procedure.no_sanitize_address) {
|
||||||
|
array_add(&p->asan_stack_locals, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zero_init) {
|
if (zero_init) {
|
||||||
|
|||||||
+13
-11
@@ -115,12 +115,13 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
|
|||||||
p->is_entry_point = false;
|
p->is_entry_point = false;
|
||||||
|
|
||||||
gbAllocator a = heap_allocator();
|
gbAllocator a = heap_allocator();
|
||||||
p->children.allocator = a;
|
p->children.allocator = a;
|
||||||
p->defer_stmts.allocator = a;
|
p->defer_stmts.allocator = a;
|
||||||
p->blocks.allocator = a;
|
p->blocks.allocator = a;
|
||||||
p->branch_blocks.allocator = a;
|
p->branch_blocks.allocator = a;
|
||||||
p->context_stack.allocator = a;
|
p->context_stack.allocator = a;
|
||||||
p->scope_stack.allocator = a;
|
p->scope_stack.allocator = a;
|
||||||
|
p->asan_stack_locals.allocator = a;
|
||||||
// map_init(&p->selector_values, 0);
|
// map_init(&p->selector_values, 0);
|
||||||
// map_init(&p->selector_addr, 0);
|
// map_init(&p->selector_addr, 0);
|
||||||
// map_init(&p->tuple_fix_map, 0);
|
// map_init(&p->tuple_fix_map, 0);
|
||||||
@@ -385,11 +386,12 @@ gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name
|
|||||||
p->is_entry_point = false;
|
p->is_entry_point = false;
|
||||||
|
|
||||||
gbAllocator a = permanent_allocator();
|
gbAllocator a = permanent_allocator();
|
||||||
p->children.allocator = a;
|
p->children.allocator = a;
|
||||||
p->defer_stmts.allocator = a;
|
p->defer_stmts.allocator = a;
|
||||||
p->blocks.allocator = a;
|
p->blocks.allocator = a;
|
||||||
p->branch_blocks.allocator = a;
|
p->branch_blocks.allocator = a;
|
||||||
p->context_stack.allocator = a;
|
p->context_stack.allocator = a;
|
||||||
|
p->asan_stack_locals.allocator = a;
|
||||||
map_init(&p->tuple_fix_map, 0);
|
map_init(&p->tuple_fix_map, 0);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2917,6 +2917,18 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo
|
|||||||
}
|
}
|
||||||
defer (p->branch_location_pos = prev_token_pos);
|
defer (p->branch_location_pos = prev_token_pos);
|
||||||
|
|
||||||
|
// TODO(lucas): In LLVM 21 use the 'use-after-scope' asan option which does this for us.
|
||||||
|
if (kind == lbDeferExit_Return) {
|
||||||
|
for_array(i, p->asan_stack_locals) {
|
||||||
|
lbValue local = p->asan_stack_locals[i];
|
||||||
|
|
||||||
|
auto args = array_make<lbValue>(temporary_allocator(), 2);
|
||||||
|
args[0] = lb_emit_conv(p, local, t_rawptr);
|
||||||
|
args[1] = lb_const_int(p->module, t_int, type_size_of(local.type->Pointer.elem));
|
||||||
|
lb_emit_runtime_call(p, "__asan_unpoison_memory_region", args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isize count = p->defer_stmts.count;
|
isize count = p->defer_stmts.count;
|
||||||
isize i = count;
|
isize i = count;
|
||||||
while (i --> 0) {
|
while (i --> 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user