mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-31 02:07:40 -04:00
- Simplify the malloc-based allocator - Correct the type signatures on malloc/calloc/realloc - Make `heap_alloc/heap_free/heap_resize` API consistent across platforms
136 lines
4.3 KiB
Odin
136 lines
4.3 KiB
Odin
#+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: uint) -> rawptr ---
|
|
@(link_name="calloc") _libc_calloc :: proc "c" (num, size: uint) -> rawptr ---
|
|
@(link_name="free") _libc_free :: proc "c" (ptr: rawptr) ---
|
|
@(link_name="realloc") _libc_realloc :: proc "c" (ptr: rawptr, size: uint) -> rawptr ---
|
|
}
|
|
|
|
@(require_results)
|
|
heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
|
if size <= 0 {
|
|
return nil
|
|
}
|
|
if zero_memory {
|
|
return _libc_calloc(1, uint(size))
|
|
} else {
|
|
return _libc_malloc(uint(size))
|
|
}
|
|
}
|
|
|
|
@(require_results)
|
|
heap_resize :: proc "contextless" (old_ptr: rawptr, old_size: int, new_size: int, zero_memory: bool = true) -> (new_ptr: rawptr) {
|
|
new_ptr = _libc_realloc(old_ptr, uint(new_size))
|
|
|
|
// Section 7.22.3.5.2 of the C17 standard: "The contents of the new object
|
|
// shall be the same as that of the old object prior to deallocation, up to
|
|
// the lesser of the new and old sizes. Any bytes in the new object beyond
|
|
// the size of the old object have indeterminate values."
|
|
//
|
|
// Therefore, we zero the memory ourselves.
|
|
if zero_memory && new_size > old_size {
|
|
intrinsics.mem_zero(rawptr(uintptr(new_ptr) + uintptr(old_size)), new_size - old_size)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
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) {
|
|
|
|
// Because malloc does not support alignment requests, and aligned_alloc
|
|
// has specific requirements for what sizes it supports, this allocator
|
|
// over-allocates by the alignment requested and stores the original
|
|
// pointer behind the address returned to the user.
|
|
|
|
switch mode {
|
|
case .Alloc, .Alloc_Non_Zeroed:
|
|
padding := max(alignment, size_of(rawptr))
|
|
ptr := heap_alloc(size + padding, mode == .Alloc)
|
|
if ptr == nil {
|
|
return nil, .Out_Of_Memory
|
|
}
|
|
shift := uintptr(padding) - uintptr(ptr) & uintptr(padding-1)
|
|
aligned_ptr := rawptr(uintptr(ptr) + shift)
|
|
([^]rawptr)(aligned_ptr)[-1] = ptr
|
|
return byte_slice(aligned_ptr, size), nil
|
|
|
|
case .Free:
|
|
if old_memory != nil {
|
|
heap_free(([^]rawptr)(old_memory)[-1])
|
|
}
|
|
|
|
case .Free_All:
|
|
return nil, .Mode_Not_Implemented
|
|
|
|
case .Resize, .Resize_Non_Zeroed:
|
|
new_padding := max(alignment, size_of(rawptr))
|
|
original_ptr := ([^]rawptr)(old_memory)[-1]
|
|
ptr: rawptr
|
|
|
|
if alignment > align_of(rawptr) {
|
|
// The alignment is in excess of what malloc/realloc will return
|
|
// for address alignment per the C standard, so we must reallocate
|
|
// manually in order to guarantee alignment for the user.
|
|
//
|
|
// Resizing through realloc simply won't work because it's possible
|
|
// that our target address originally only needed a padding of 8
|
|
// bytes, but if we expand the memory used and the address is moved,
|
|
// we may then need 16 bytes for proper alignment, for example.
|
|
//
|
|
// We'll copy the old data later.
|
|
ptr = heap_alloc(size + new_padding, mode == .Resize)
|
|
|
|
} else {
|
|
real_old_size := size_of(rawptr) + old_size
|
|
real_new_size := new_padding + size
|
|
|
|
ptr = heap_resize(original_ptr, real_old_size, real_new_size, mode == .Resize)
|
|
}
|
|
|
|
shift := uintptr(new_padding) - uintptr(ptr) & uintptr(new_padding-1)
|
|
aligned_ptr := rawptr(uintptr(ptr) + shift)
|
|
([^]rawptr)(aligned_ptr)[-1] = ptr
|
|
|
|
if alignment > align_of(rawptr) {
|
|
intrinsics.mem_copy_non_overlapping(aligned_ptr, old_memory, min(size, old_size))
|
|
heap_free(original_ptr)
|
|
}
|
|
|
|
return byte_slice(aligned_ptr, size), nil
|
|
|
|
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
|
|
}
|