diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin index 2a6029284..fba56c10a 100644 --- a/base/runtime/heap_allocator.odin +++ b/base/runtime/heap_allocator.odin @@ -1,3 +1,4 @@ +#+build !orca package runtime import "base:intrinsics" diff --git a/base/runtime/heap_allocator_control.odin b/base/runtime/heap_allocator_control.odin index 737870c04..f7ead8f57 100644 --- a/base/runtime/heap_allocator_control.odin +++ b/base/runtime/heap_allocator_control.odin @@ -1,3 +1,4 @@ +#+build !orca package runtime import "base:intrinsics" diff --git a/base/runtime/heap_allocator_debugging.odin b/base/runtime/heap_allocator_debugging.odin index 98225fddb..e2315c37a 100644 --- a/base/runtime/heap_allocator_debugging.odin +++ b/base/runtime/heap_allocator_debugging.odin @@ -1,3 +1,4 @@ +#+build !orca package runtime import "base:intrinsics" diff --git a/base/runtime/heap_allocator_fallback.odin b/base/runtime/heap_allocator_fallback.odin new file mode 100644 index 000000000..129cd9d86 --- /dev/null +++ b/base/runtime/heap_allocator_fallback.odin @@ -0,0 +1,138 @@ +#+build orca +package runtime + +// This is the libc malloc-based Odin allocator, used as a fallback for +// platforms that do not support virtual memory, superpages, or aligned +// allocation, or for platforms where we would prefer to use the system's +// built-in allocator through the malloc interface. + +import "base:intrinsics" + +foreign { + @(link_name="malloc") _libc_malloc :: proc "c" (size: int) -> rawptr --- + @(link_name="calloc") _libc_calloc :: proc "c" (num, size: int) -> rawptr --- + @(link_name="free") _libc_free :: proc "c" (ptr: rawptr) --- + @(link_name="realloc") _libc_realloc :: proc "c" (ptr: rawptr, size: int) -> rawptr --- +} + +heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + if size <= 0 { + return nil + } + if zero_memory { + return _libc_calloc(1, size) + } else { + return _libc_malloc(size) + } +} + +heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { + return _libc_realloc(ptr, new_size) +} + +heap_free :: proc "contextless" (ptr: rawptr) { + _libc_free(ptr) +} + +heap_allocator :: proc() -> Allocator { + return { + data = nil, + procedure = heap_allocator_proc, + } +} + +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) { + // NOTE(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 { + mem_copy_non_overlapping(aligned_mem, old_ptr, min(old_size, size)) + aligned_free(old_ptr) + } + + return 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 +} diff --git a/base/runtime/heap_allocator_implementation.odin b/base/runtime/heap_allocator_implementation.odin index d7148d8d2..59738fcf6 100644 --- a/base/runtime/heap_allocator_implementation.odin +++ b/base/runtime/heap_allocator_implementation.odin @@ -1,3 +1,4 @@ +#+build !orca package runtime import "base:intrinsics" diff --git a/base/runtime/heap_allocator_info.odin b/base/runtime/heap_allocator_info.odin index 3c69861de..8fcc9cfb6 100644 --- a/base/runtime/heap_allocator_info.odin +++ b/base/runtime/heap_allocator_info.odin @@ -1,3 +1,4 @@ +#+build !orca package runtime import "base:intrinsics"