mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-26 06:51:34 +00:00
Add native heap allocator
- Add the test bench for the allocator - Move old allocator code to the test bench - Fix `heap_resize` usage in `os2/env_linux.odin` to fit new API requiring `old_size`
This commit is contained in:
@@ -9,111 +9,65 @@ heap_allocator :: proc() -> Allocator {
|
||||
}
|
||||
}
|
||||
|
||||
heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
//
|
||||
// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
|
||||
// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
|
||||
// padding. We also store the original pointer returned by heap_alloc right before
|
||||
// the pointer we return to the user.
|
||||
//
|
||||
|
||||
aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr, old_size: int, zero_memory := true) -> ([]byte, Allocator_Error) {
|
||||
// Not(flysand): We need to reserve enough space for alignment, which
|
||||
// includes the user data itself, the space to store the pointer to
|
||||
// allocation start, as well as the padding required to align both
|
||||
// the user data and the pointer.
|
||||
a := max(alignment, align_of(rawptr))
|
||||
space := a-1 + size_of(rawptr) + size
|
||||
allocated_mem: rawptr
|
||||
|
||||
force_copy := old_ptr != nil && alignment > align_of(rawptr)
|
||||
|
||||
if old_ptr != nil && !force_copy {
|
||||
original_old_ptr := ([^]rawptr)(old_ptr)[-1]
|
||||
allocated_mem = heap_resize(original_old_ptr, space)
|
||||
} else {
|
||||
allocated_mem = heap_alloc(space, zero_memory)
|
||||
}
|
||||
aligned_mem := rawptr(([^]u8)(allocated_mem)[size_of(rawptr):])
|
||||
|
||||
ptr := uintptr(aligned_mem)
|
||||
aligned_ptr := (ptr + uintptr(a)-1) & ~(uintptr(a)-1)
|
||||
if allocated_mem == nil {
|
||||
aligned_free(old_ptr)
|
||||
aligned_free(allocated_mem)
|
||||
heap_allocator_proc :: proc(
|
||||
allocator_data: rawptr,
|
||||
mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr,
|
||||
old_size: int,
|
||||
loc := #caller_location,
|
||||
) -> ([]byte, Allocator_Error) {
|
||||
assert(alignment <= HEAP_MAX_ALIGNMENT, "Heap allocation alignment beyond HEAP_MAX_ALIGNMENT bytes is not supported.", loc = loc)
|
||||
assert(alignment >= 0, "Alignment must be greater than or equal to zero.", loc = loc)
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
// All allocations are aligned to at least their size up to
|
||||
// `HEAP_MAX_ALIGNMENT`, and by virtue of binary arithmetic, any
|
||||
// address aligned to N will also be aligned to N>>1.
|
||||
//
|
||||
// Therefore, we have no book-keeping costs for alignment.
|
||||
ptr := heap_alloc(max(size, alignment))
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
aligned_mem = rawptr(aligned_ptr)
|
||||
([^]rawptr)(aligned_mem)[-1] = allocated_mem
|
||||
|
||||
if force_copy {
|
||||
mem_copy_non_overlapping(aligned_mem, old_ptr, min(old_size, size))
|
||||
aligned_free(old_ptr)
|
||||
return transmute([]byte)Raw_Slice{ data = ptr, len = size }, nil
|
||||
case .Alloc_Non_Zeroed:
|
||||
ptr := heap_alloc(max(size, alignment), zero = false)
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
return byte_slice(aligned_mem, size), nil
|
||||
}
|
||||
|
||||
aligned_free :: proc(p: rawptr) {
|
||||
if p != nil {
|
||||
heap_free(([^]rawptr)(p)[-1])
|
||||
return transmute([]byte)Raw_Slice{ data = ptr, len = size }, nil
|
||||
case .Resize:
|
||||
ptr := heap_resize(old_memory, old_size, max(size, alignment))
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
}
|
||||
|
||||
aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory := true) -> (new_memory: []byte, err: Allocator_Error) {
|
||||
if p == nil {
|
||||
return aligned_alloc(new_size, new_alignment, nil, old_size, zero_memory)
|
||||
return transmute([]byte)Raw_Slice{ data = ptr, len = size }, nil
|
||||
case .Resize_Non_Zeroed:
|
||||
ptr := heap_resize(old_memory, old_size, max(size, alignment), zero = false)
|
||||
if ptr == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
new_memory = aligned_alloc(new_size, new_alignment, p, old_size, zero_memory) or_return
|
||||
|
||||
// NOTE: heap_resize does not zero the new memory, so we do it
|
||||
if zero_memory && new_size > old_size {
|
||||
new_region := raw_data(new_memory[old_size:])
|
||||
intrinsics.mem_zero(new_region, new_size - old_size)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return aligned_alloc(size, alignment, nil, 0, mode == .Alloc)
|
||||
|
||||
return transmute([]byte)Raw_Slice{ data = ptr, len = size }, nil
|
||||
case .Free:
|
||||
aligned_free(old_memory)
|
||||
|
||||
heap_free(old_memory)
|
||||
case .Free_All:
|
||||
return nil, .Mode_Not_Implemented
|
||||
|
||||
case .Resize, .Resize_Non_Zeroed:
|
||||
return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize)
|
||||
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features}
|
||||
set^ = {
|
||||
.Alloc,
|
||||
.Alloc_Non_Zeroed,
|
||||
.Resize,
|
||||
.Resize_Non_Zeroed,
|
||||
.Free,
|
||||
.Query_Features,
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case .Query_Info:
|
||||
return nil, .Mode_Not_Implemented
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
return _heap_alloc(size, zero_memory)
|
||||
}
|
||||
|
||||
heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||
return _heap_resize(ptr, new_size)
|
||||
}
|
||||
|
||||
heap_free :: proc "contextless" (ptr: rawptr) {
|
||||
_heap_free(ptr)
|
||||
}
|
||||
89
base/runtime/heap_allocator_control.odin
Normal file
89
base/runtime/heap_allocator_control.odin
Normal file
@@ -0,0 +1,89 @@
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
/*
|
||||
Merge all remote frees then free as many slabs as possible.
|
||||
|
||||
This bypasses any heuristics that keep slabs setup.
|
||||
|
||||
Returns true if the superpage was emptied and freed.
|
||||
*/
|
||||
@(private)
|
||||
compact_superpage :: proc "contextless" (superpage: ^Heap_Superpage) -> (freed: bool) {
|
||||
for i := 0; i < HEAP_SLAB_COUNT; /**/ {
|
||||
slab := heap_superpage_index_slab(superpage, i)
|
||||
|
||||
if slab.bin_size > HEAP_MAX_BIN_SIZE {
|
||||
// Skip contiguous slabs.
|
||||
i += heap_slabs_needed_for_size(slab.bin_size)
|
||||
} else if slab.bin_size > 0 {
|
||||
i += 1
|
||||
} else {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
|
||||
slab_is_cached := slab.free_bins > 0
|
||||
heap_merge_remote_frees(slab)
|
||||
|
||||
if slab.free_bins == slab.max_bins {
|
||||
if slab.bin_size > HEAP_MAX_BIN_SIZE {
|
||||
heap_free_wide_slab(superpage, slab)
|
||||
} else {
|
||||
if slab_is_cached {
|
||||
heap_cache_remove_slab(slab, heap_bin_size_to_rank(slab.bin_size))
|
||||
}
|
||||
heap_free_slab(superpage, slab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if superpage.free_slabs == HEAP_SLAB_COUNT && !superpage.cache_block.in_use {
|
||||
heap_free_superpage(superpage)
|
||||
freed = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Merge all remote frees then free as many slabs and superpages as possible.
|
||||
|
||||
This bypasses any heuristics that keep slabs setup.
|
||||
*/
|
||||
compact_heap :: proc "contextless" () {
|
||||
superpage := local_heap
|
||||
for {
|
||||
if superpage == nil {
|
||||
return
|
||||
}
|
||||
next_superpage := superpage.next
|
||||
compact_superpage(superpage)
|
||||
superpage = next_superpage
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Free any empty superpages in the orphanage.
|
||||
|
||||
This procedure assumes there won't ever be more than 128 superpages in the
|
||||
orphanage. This limitation is due to the avoidance of heap allocation.
|
||||
*/
|
||||
compact_heap_orphanage :: proc "contextless" () {
|
||||
// First, try to empty the orphanage so that we can evaluate each superpage.
|
||||
buffer: [128]^Heap_Superpage
|
||||
for i := 0; i < len(buffer); i += 1 {
|
||||
buffer[i] = heap_pop_orphan()
|
||||
if buffer[i] == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Next, compact each superpage and push it back to the orphanage if it was
|
||||
// not freed.
|
||||
for superpage in buffer {
|
||||
if !compact_superpage(superpage) {
|
||||
heap_push_orphan(superpage)
|
||||
}
|
||||
}
|
||||
}
|
||||
93
base/runtime/heap_allocator_debugging.odin
Normal file
93
base/runtime/heap_allocator_debugging.odin
Normal file
@@ -0,0 +1,93 @@
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
ODIN_DEBUG_HEAP :: #config(ODIN_DEBUG_HEAP, false)
|
||||
|
||||
Heap_Code_Coverage_Type :: enum {
|
||||
Alloc_Bin,
|
||||
Alloc_Collected_Remote_Frees,
|
||||
Alloc_Heap_Initialized,
|
||||
Alloc_Huge,
|
||||
Alloc_Slab_Wide,
|
||||
Alloc_Slab_Wide_Needed_New_Superpage,
|
||||
Alloc_Slab_Wide_Used_Available_Superpage,
|
||||
Alloc_Zeroed_Memory,
|
||||
Freed_Bin_Freed_Slab_Which_Was_Fully_Used,
|
||||
Freed_Bin_Freed_Superpage,
|
||||
Freed_Bin_Reopened_Full_Slab,
|
||||
Freed_Bin_Updated_Slab_Next_Free_Sector,
|
||||
Freed_Huge_Allocation,
|
||||
Freed_Wide_Slab,
|
||||
Heap_Expanded_Cache_Data,
|
||||
Huge_Alloc_Size_Adjusted,
|
||||
Huge_Alloc_Size_Set_To_Superpage,
|
||||
Merged_Remote_Frees,
|
||||
Orphaned_Superpage_Freed_Slab,
|
||||
Orphaned_Superpage_Merged_Remote_Frees,
|
||||
Remotely_Freed_Bin,
|
||||
Remotely_Freed_Bin_Caused_Remote_Superpage_Caching,
|
||||
Remotely_Freed_Wide_Slab,
|
||||
Resize_Caused_Memory_Zeroing,
|
||||
Resize_Crossed_Size_Categories,
|
||||
Resize_Huge,
|
||||
Resize_Huge_Caused_Memory_Zeroing,
|
||||
Resize_Huge_Size_Adjusted,
|
||||
Resize_Huge_Size_Set_To_Superpage,
|
||||
Resize_Kept_Old_Pointer,
|
||||
Resize_Wide_Slab_Caused_Memory_Zeroing,
|
||||
Resize_Wide_Slab_Expanded_In_Place,
|
||||
Resize_Wide_Slab_Failed_To_Find_Contiguous_Expansion,
|
||||
Resize_Wide_Slab_From_Remote_Thread,
|
||||
Resize_Wide_Slab_Kept_Old_Pointer,
|
||||
Resize_Wide_Slab_Shrunk_In_Place,
|
||||
Slab_Adjusted_For_Partial_Sector,
|
||||
Superpage_Add_Remote_Free_Guarded_With_Masterless,
|
||||
Superpage_Add_Remote_Free_Guarded_With_Set,
|
||||
Superpage_Added_Remote_Free,
|
||||
Superpage_Added_To_Open_Cache_By_Freeing_Wide_Slab,
|
||||
Superpage_Added_To_Open_Cache_By_Resizing_Wide_Slab,
|
||||
Superpage_Added_To_Open_Cache_By_Slab,
|
||||
Superpage_Adopted_From_Orphanage,
|
||||
Superpage_Created_By_Empty_Orphanage,
|
||||
Superpage_Freed_By_Exiting_Thread,
|
||||
Superpage_Freed_By_Wide_Slab,
|
||||
Superpage_Freed_On_Full_Orphanage,
|
||||
Superpage_Linked,
|
||||
Superpage_Cache_Block_Cleared,
|
||||
Superpage_Orphaned_By_Exiting_Thread,
|
||||
Superpage_Pushed_To_Orphanage,
|
||||
Superpage_Registered_With_Free_Slabs,
|
||||
Superpage_Registered_With_Slab_In_Use,
|
||||
Superpage_Removed_From_Open_Cache_By_Slab,
|
||||
Superpage_Unlinked_Non_Tail,
|
||||
Superpage_Unlinked_Tail,
|
||||
Superpage_Unregistered,
|
||||
Superpage_Updated_Next_Free_Slab_Index,
|
||||
Superpage_Updated_Next_Free_Slab_Index_As_Empty,
|
||||
}
|
||||
|
||||
when ODIN_DEBUG_HEAP {
|
||||
heap_global_code_coverage: [Heap_Code_Coverage_Type]int // atomic
|
||||
}
|
||||
|
||||
@(private, disabled=!ODIN_DEBUG_HEAP)
|
||||
heap_debug_cover :: #force_inline proc "contextless" (type: Heap_Code_Coverage_Type) {
|
||||
when ODIN_DEBUG_HEAP {
|
||||
intrinsics.atomic_add_explicit(&heap_global_code_coverage[type], 1, .Release)
|
||||
}
|
||||
}
|
||||
|
||||
_check_heap_code_coverage :: proc "contextless" () -> bool {
|
||||
when ODIN_DEBUG_HEAP {
|
||||
intrinsics.atomic_thread_fence(.Seq_Cst)
|
||||
for t in heap_global_code_coverage {
|
||||
if t == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
panic_contextless("ODIN_DEBUG_HEAP is not enabled, therefore the results of this procedure are meaningless.")
|
||||
}
|
||||
}
|
||||
1880
base/runtime/heap_allocator_implementation.odin
Normal file
1880
base/runtime/heap_allocator_implementation.odin
Normal file
File diff suppressed because it is too large
Load Diff
156
base/runtime/heap_allocator_info.odin
Normal file
156
base/runtime/heap_allocator_info.odin
Normal file
@@ -0,0 +1,156 @@
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
/*
|
||||
Heap_Info provides metrics on a single thread's heap memory usage.
|
||||
*/
|
||||
Heap_Info :: struct {
|
||||
total_memory_allocated_from_system: int `fmt:"M"`,
|
||||
total_memory_used_for_book_keeping: int `fmt:"M"`,
|
||||
total_memory_in_use: int `fmt:"M"`,
|
||||
total_memory_free: int `fmt:"M"`,
|
||||
total_memory_dirty: int `fmt:"M"`,
|
||||
total_memory_remotely_free: int `fmt:"M"`,
|
||||
|
||||
total_superpages: int,
|
||||
total_superpages_dedicated_to_heap_cache: int,
|
||||
total_huge_allocations: int,
|
||||
total_slabs: int,
|
||||
total_slabs_in_use: int,
|
||||
|
||||
total_dirty_bins: int,
|
||||
total_free_bins: int,
|
||||
total_bins_in_use: int,
|
||||
total_remote_free_bins: int,
|
||||
|
||||
heap_slab_map_entries: int,
|
||||
heap_superpages_with_free_slabs: int,
|
||||
heap_slabs_with_remote_frees: int,
|
||||
}
|
||||
|
||||
/*
|
||||
Get information about the current thread's heap.
|
||||
|
||||
This will do additional sanity checking on the heap if assertions are enabled.
|
||||
*/
|
||||
@(require_results)
|
||||
get_local_heap_info :: proc "contextless" () -> (info: Heap_Info) {
|
||||
if local_heap_cache != nil {
|
||||
cache := local_heap_cache
|
||||
slab_map_terminated: [HEAP_BIN_RANKS]bool
|
||||
|
||||
for {
|
||||
for rank := 0; rank < HEAP_BIN_RANKS; rank += 1 {
|
||||
for i := 0; i < HEAP_CACHE_SLAB_MAP_STRIDE; i += 1 {
|
||||
slab := cache.slab_map[rank * HEAP_CACHE_SLAB_MAP_STRIDE + i]
|
||||
if slab_map_terminated[rank] {
|
||||
assert_contextless(slab == nil, "The heap allocator has a gap in its slab map.")
|
||||
} else if slab == nil {
|
||||
slab_map_terminated[rank] = true
|
||||
} else {
|
||||
info.heap_slab_map_entries += 1
|
||||
assert_contextless(slab.bin_size != 0, "The heap allocator has an empty slab in its slab map.")
|
||||
assert_contextless(slab.bin_size == 1 << (HEAP_MIN_BIN_SHIFT + uint(rank)), "The heap allocator has a slab in the wrong sub-array of the slab map.")
|
||||
}
|
||||
}
|
||||
}
|
||||
for superpage in cache.superpages_with_free_slabs {
|
||||
if superpage != nil {
|
||||
info.heap_superpages_with_free_slabs += 1
|
||||
}
|
||||
}
|
||||
for i in 0..<len(cache.superpages_with_remote_frees) {
|
||||
if intrinsics.atomic_load_explicit(&cache.superpages_with_remote_frees[i], .Seq_Cst) != nil {
|
||||
info.heap_slabs_with_remote_frees += 1
|
||||
}
|
||||
}
|
||||
if cache.next_cache_block == nil {
|
||||
break
|
||||
}
|
||||
cache = cache.next_cache_block
|
||||
}
|
||||
}
|
||||
|
||||
superpage := local_heap
|
||||
for {
|
||||
if superpage == nil {
|
||||
break
|
||||
}
|
||||
assert_contextless(superpage.owner == get_current_thread_id(), "The heap allocator for this thread has a superpage that belongs to another thread.")
|
||||
info.total_superpages += 1
|
||||
if superpage.huge_size > 0 {
|
||||
info.total_huge_allocations += 1
|
||||
info.total_memory_allocated_from_system += superpage.huge_size
|
||||
info.total_memory_in_use += superpage.huge_size - HEAP_HUGE_ALLOCATION_BOOK_KEEPING
|
||||
info.total_memory_used_for_book_keeping += HEAP_HUGE_ALLOCATION_BOOK_KEEPING
|
||||
} else {
|
||||
if superpage.cache_block.in_use {
|
||||
info.total_superpages_dedicated_to_heap_cache += 1
|
||||
}
|
||||
info.total_memory_allocated_from_system += SUPERPAGE_SIZE
|
||||
for i := 0; i < HEAP_SLAB_COUNT; /**/ {
|
||||
slab := heap_superpage_index_slab(superpage, i)
|
||||
|
||||
if slab.bin_size != 0 {
|
||||
info.total_slabs_in_use += 1
|
||||
info.total_memory_in_use += slab.bin_size * (slab.max_bins - slab.free_bins)
|
||||
info.total_memory_free += slab.bin_size * slab.free_bins
|
||||
info.total_memory_dirty += slab.bin_size * slab.dirty_bins
|
||||
info.total_bins_in_use += slab.max_bins - slab.free_bins
|
||||
info.total_free_bins += slab.free_bins
|
||||
info.total_dirty_bins += slab.dirty_bins
|
||||
assert_contextless(slab.dirty_bins >= slab.max_bins - slab.free_bins, "A slab of the heap allocator has a number of dirty bins which is not equivalent to the number of its total bins minus the number of free bins.")
|
||||
// Account for the bitmaps used by the Slab.
|
||||
info.total_memory_used_for_book_keeping += int(slab.data - uintptr(slab))
|
||||
// Account for the space not used by the bins or the bitmaps.
|
||||
n := int(slab.data - uintptr(slab) + uintptr(slab.max_bins * slab.bin_size))
|
||||
if slab.bin_size > HEAP_MAX_BIN_SIZE {
|
||||
info.total_memory_used_for_book_keeping += heap_slabs_needed_for_size(slab.bin_size) * HEAP_SLAB_SIZE - n
|
||||
} else {
|
||||
info.total_memory_used_for_book_keeping += HEAP_SLAB_SIZE - n
|
||||
}
|
||||
remote_free_bins := 0
|
||||
for j in 0..<slab.sectors {
|
||||
remote_free_bins += int(intrinsics.count_ones(intrinsics.atomic_load_explicit(&slab.remote_free[j], .Seq_Cst)))
|
||||
}
|
||||
info.total_remote_free_bins += remote_free_bins
|
||||
info.total_memory_remotely_free += slab.bin_size * remote_free_bins
|
||||
} else {
|
||||
// When the slab is allocated, the book-keeping bitmaps and
|
||||
// the Slab struct itself will take some of this space, so
|
||||
// it's only an approximation of what is possible.
|
||||
info.total_memory_free += HEAP_SLAB_SIZE
|
||||
when !ODIN_DISABLE_ASSERT {
|
||||
if !slab.is_dirty {
|
||||
// Verify that the slab is actually zeroed out ahead of its index field.
|
||||
ptr := cast([^]u8)rawptr(uintptr(slab) + size_of(int))
|
||||
for k in 0..<HEAP_SLAB_SIZE - size_of(int) {
|
||||
assert_contextless(ptr[k] == 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if slab.bin_size > HEAP_MAX_BIN_SIZE {
|
||||
// Skip contiguous slabs.
|
||||
i += heap_slabs_needed_for_size(slab.bin_size)
|
||||
} else {
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
// Every superpage has to sacrifice one Slab's worth of space so
|
||||
// that they're all aligned.
|
||||
info.total_memory_used_for_book_keeping += HEAP_SLAB_SIZE
|
||||
info.total_slabs += HEAP_SLAB_COUNT
|
||||
}
|
||||
superpage = superpage.next
|
||||
}
|
||||
|
||||
assert_contextless(info.total_memory_allocated_from_system == info.total_memory_used_for_book_keeping + info.total_memory_in_use + info.total_memory_free, "The heap allocator's metrics for total memory in use, free, and used for book-keeping do not add up to the total memory allocated from the operating system.")
|
||||
|
||||
if local_heap_cache != nil {
|
||||
assert_contextless(info.total_superpages == local_heap_cache.owned_superpages)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -74,7 +74,7 @@ _set_env :: proc(key, v_new: string) -> Error {
|
||||
// wasn't in the environment in the first place.
|
||||
k_addr, v_addr := _kv_addr_from_val(v_curr, key)
|
||||
if len(v_new) > len(v_curr) {
|
||||
k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size))
|
||||
k_addr = ([^]u8)(runtime.heap_resize(k_addr, len(v_curr), kv_size))
|
||||
if k_addr == nil {
|
||||
return .Out_Of_Memory
|
||||
}
|
||||
|
||||
127
tests/heap_allocator/libc/heap_allocator.odin
Normal file
127
tests/heap_allocator/libc/heap_allocator.odin
Normal file
@@ -0,0 +1,127 @@
|
||||
package tests_heap_allocator_libc
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:mem"
|
||||
|
||||
// This package contains the old libc malloc-based allocator, for comparison.
|
||||
|
||||
Allocator :: runtime.Allocator
|
||||
Allocator_Mode :: runtime.Allocator_Mode
|
||||
Allocator_Mode_Set :: runtime.Allocator_Mode_Set
|
||||
Allocator_Error :: runtime.Allocator_Error
|
||||
|
||||
libc_allocator :: proc() -> Allocator {
|
||||
return Allocator{
|
||||
procedure = libc_allocator_proc,
|
||||
data = nil,
|
||||
}
|
||||
}
|
||||
|
||||
libc_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
//
|
||||
// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
|
||||
// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
|
||||
// padding. We also store the original pointer returned by heap_alloc right before
|
||||
// the pointer we return to the user.
|
||||
//
|
||||
|
||||
aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr, old_size: int, zero_memory := true) -> ([]byte, Allocator_Error) {
|
||||
// Not(flysand): We need to reserve enough space for alignment, which
|
||||
// includes the user data itself, the space to store the pointer to
|
||||
// allocation start, as well as the padding required to align both
|
||||
// the user data and the pointer.
|
||||
a := max(alignment, align_of(rawptr))
|
||||
space := a-1 + size_of(rawptr) + size
|
||||
allocated_mem: rawptr
|
||||
|
||||
force_copy := old_ptr != nil && alignment > align_of(rawptr)
|
||||
|
||||
if old_ptr != nil && !force_copy {
|
||||
original_old_ptr := ([^]rawptr)(old_ptr)[-1]
|
||||
allocated_mem = heap_resize(original_old_ptr, space)
|
||||
} else {
|
||||
allocated_mem = heap_alloc(space, zero_memory)
|
||||
}
|
||||
aligned_mem := rawptr(([^]u8)(allocated_mem)[size_of(rawptr):])
|
||||
|
||||
ptr := uintptr(aligned_mem)
|
||||
aligned_ptr := (ptr + uintptr(a)-1) & ~(uintptr(a)-1)
|
||||
if allocated_mem == nil {
|
||||
aligned_free(old_ptr)
|
||||
aligned_free(allocated_mem)
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
aligned_mem = rawptr(aligned_ptr)
|
||||
([^]rawptr)(aligned_mem)[-1] = allocated_mem
|
||||
|
||||
if force_copy {
|
||||
runtime.mem_copy_non_overlapping(aligned_mem, old_ptr, min(old_size, size))
|
||||
aligned_free(old_ptr)
|
||||
}
|
||||
|
||||
return mem.byte_slice(aligned_mem, size), nil
|
||||
}
|
||||
|
||||
aligned_free :: proc(p: rawptr) {
|
||||
if p != nil {
|
||||
heap_free(([^]rawptr)(p)[-1])
|
||||
}
|
||||
}
|
||||
|
||||
aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory := true) -> (new_memory: []byte, err: Allocator_Error) {
|
||||
if p == nil {
|
||||
return aligned_alloc(new_size, new_alignment, nil, old_size, zero_memory)
|
||||
}
|
||||
|
||||
new_memory = aligned_alloc(new_size, new_alignment, p, old_size, zero_memory) or_return
|
||||
|
||||
// NOTE: heap_resize does not zero the new memory, so we do it
|
||||
if zero_memory && new_size > old_size {
|
||||
new_region := raw_data(new_memory[old_size:])
|
||||
intrinsics.mem_zero(new_region, new_size - old_size)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return aligned_alloc(size, alignment, nil, 0, mode == .Alloc)
|
||||
|
||||
case .Free:
|
||||
aligned_free(old_memory)
|
||||
|
||||
case .Free_All:
|
||||
return nil, .Mode_Not_Implemented
|
||||
|
||||
case .Resize, .Resize_Non_Zeroed:
|
||||
return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize)
|
||||
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case .Query_Info:
|
||||
return nil, .Mode_Not_Implemented
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
return _heap_alloc(size, zero_memory)
|
||||
}
|
||||
|
||||
heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||
return _heap_resize(ptr, new_size)
|
||||
}
|
||||
|
||||
heap_free :: proc "contextless" (ptr: rawptr) {
|
||||
_heap_free(ptr)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#+build orca
|
||||
#+private
|
||||
package runtime
|
||||
package tests_heap_allocator_libc
|
||||
|
||||
foreign {
|
||||
@(link_name="malloc") _orca_malloc :: proc "c" (size: int) -> rawptr ---
|
||||
@@ -1,6 +1,6 @@
|
||||
#+build js, wasi, freestanding, essence
|
||||
#+private
|
||||
package runtime
|
||||
package tests_heap_allocator_libc
|
||||
|
||||
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
context = default_context()
|
||||
@@ -1,6 +1,6 @@
|
||||
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||
#+private
|
||||
package runtime
|
||||
package tests_heap_allocator_libc
|
||||
|
||||
when ODIN_OS == .Darwin {
|
||||
foreign import libc "system:System.framework"
|
||||
@@ -1,4 +1,4 @@
|
||||
package runtime
|
||||
package tests_heap_allocator_libc
|
||||
|
||||
import "../sanitizer"
|
||||
|
||||
1493
tests/heap_allocator/test_bench.odin
Normal file
1493
tests/heap_allocator/test_bench.odin
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user