mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
0.5.5 Disallow multiple `_` in a row in digits, e.g. `1__000`. #1138. Fixed toposort example. Struct/union members now correctly rejects members without storage size #1147. `math::pow` will now correctly promote integer arguments. `math::pow` will now correctly promote integer arguments. Added `new_aligned` and `alloc_aligned` functions to prevent accidental under-alignment when allocating simd. Pointer difference would fail where alignment != size (structs etc) #1150. Add test that overalignment actually works for lists. Fixed array calculation for npot2 vectors. Use native aligned alloc on Windows and POSIX. Deprecates "offset". Simplification of the Allocator interface.
444 lines
12 KiB
C
444 lines
12 KiB
C
module std::core::mem::allocator;
|
|
|
|
const DEFAULT_SIZE_PREFIX = usz.sizeof;
|
|
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
|
|
|
|
struct TrackingEnv
|
|
{
|
|
String file;
|
|
String function;
|
|
uint line;
|
|
}
|
|
|
|
|
|
interface Allocator
|
|
{
|
|
fn void reset(usz mark) @optional;
|
|
fn usz mark() @optional;
|
|
/**
|
|
* @require !alignment || math::is_power_of_2(alignment)
|
|
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
|
* @require offset == 0 `offset no longer supported`
|
|
* @require size > 0
|
|
**/
|
|
fn void*! acquire(usz size, bool clear, usz alignment, usz offset);
|
|
/**
|
|
* @require !alignment || math::is_power_of_2(alignment)
|
|
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
|
|
* @require offset == 0 `offset no longer supported`
|
|
* @require ptr != null
|
|
* @require new_size > 0
|
|
**/
|
|
fn void*! resize(void* ptr, usz new_size, usz alignment, usz offset);
|
|
/**
|
|
* @require ptr != null
|
|
**/
|
|
fn void release(void* ptr, bool aligned);
|
|
}
|
|
|
|
def MemoryAllocFn = fn char[]!(usz);
|
|
|
|
fault AllocationFailure
|
|
{
|
|
OUT_OF_MEMORY,
|
|
CHUNK_TOO_LARGE,
|
|
}
|
|
|
|
fn usz alignment_for_allocation(usz alignment) @inline @private
|
|
{
|
|
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? alignment = mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
|
}
|
|
|
|
macro void* malloc(Allocator* allocator, usz size) @nodiscard
|
|
{
|
|
return malloc_try(allocator, size)!!;
|
|
}
|
|
|
|
macro void*! malloc_try(Allocator* allocator, usz size) @nodiscard
|
|
{
|
|
if (!size) return null;
|
|
$if env::TESTING:
|
|
char* data = allocator.acquire(size, false, 0, 0)!;
|
|
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
|
return data;
|
|
$else
|
|
return allocator.acquire(size, false, 0, 0);
|
|
$endif
|
|
}
|
|
|
|
macro void* calloc(Allocator* allocator, usz size) @nodiscard
|
|
{
|
|
return calloc_try(allocator, size)!!;
|
|
}
|
|
|
|
macro void*! calloc_try(Allocator* allocator, usz size) @nodiscard
|
|
{
|
|
if (!size) return null;
|
|
return allocator.acquire(size, true, 0, 0);
|
|
}
|
|
|
|
macro void* realloc(Allocator* allocator, void* ptr, usz new_size) @nodiscard
|
|
{
|
|
return realloc_try(allocator, ptr, new_size)!!;
|
|
}
|
|
|
|
macro void*! realloc_try(Allocator* allocator, void* ptr, usz new_size) @nodiscard
|
|
{
|
|
if (!new_size)
|
|
{
|
|
free(allocator, ptr);
|
|
return null;
|
|
}
|
|
if (!ptr) return allocator.acquire(new_size, false, 0, 0);
|
|
return allocator.resize(ptr, new_size, 0, 0);
|
|
}
|
|
|
|
macro void free(Allocator* allocator, void* ptr)
|
|
{
|
|
if (!ptr) return;
|
|
$if env::TESTING:
|
|
((char*)ptr)[0] = 0xBA;
|
|
$endif
|
|
allocator.release(ptr, false);
|
|
}
|
|
|
|
macro void*! malloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @nodiscard
|
|
{
|
|
if (!size) return null;
|
|
$if env::TESTING:
|
|
char* data = allocator.acquire(size, false, alignment, offset)!;
|
|
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
|
|
return data;
|
|
$else
|
|
return allocator.acquire(size, false, alignment, offset);
|
|
$endif
|
|
}
|
|
|
|
macro void*! calloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @nodiscard
|
|
{
|
|
if (!size) return null;
|
|
return allocator.acquire(size, true, alignment, offset);
|
|
}
|
|
|
|
macro void*! realloc_aligned(Allocator* allocator, void* ptr, usz new_size, usz alignment, usz offset = 0) @nodiscard
|
|
{
|
|
if (!new_size)
|
|
{
|
|
free_aligned(allocator, ptr);
|
|
return null;
|
|
}
|
|
if (!ptr)
|
|
{
|
|
return malloc_aligned(allocator, new_size, alignment);
|
|
}
|
|
return allocator.resize(ptr, new_size, alignment, offset);
|
|
}
|
|
|
|
macro void free_aligned(Allocator* allocator, void* ptr)
|
|
{
|
|
if (!ptr) return;
|
|
$if env::TESTING:
|
|
((char*)ptr)[0] = 0xBA;
|
|
$endif
|
|
allocator.release(ptr, true);
|
|
}
|
|
|
|
/**
|
|
* @require $vacount < 2 : "Too many arguments."
|
|
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
|
|
**/
|
|
macro new(Allocator* allocator, $Type, ...) @nodiscard
|
|
{
|
|
$if $vacount == 0:
|
|
return ($Type*)calloc(allocator, $Type.sizeof);
|
|
$else
|
|
$Type* val = malloc(allocator, $Type.sizeof);
|
|
*val = $vaexpr(0);
|
|
return val;
|
|
$endif
|
|
}
|
|
|
|
/**
|
|
* @require $vacount < 2 : "Too many arguments."
|
|
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
|
|
**/
|
|
macro new_try(Allocator* allocator, $Type, ...) @nodiscard
|
|
{
|
|
$if $vacount == 0:
|
|
return ($Type*)calloc_try(allocator, $Type.sizeof);
|
|
$else
|
|
$Type* val = malloc_try(allocator, $Type.sizeof)!;
|
|
*val = $vaexpr(0);
|
|
return val;
|
|
$endif
|
|
}
|
|
|
|
macro new_with_padding(Allocator* allocator, $Type, usz padding) @nodiscard
|
|
{
|
|
return ($Type*)calloc_try(allocator, $Type.sizeof + padding);
|
|
}
|
|
|
|
macro alloc(Allocator* allocator, $Type) @nodiscard
|
|
{
|
|
return ($Type*)malloc(allocator, $Type.sizeof);
|
|
}
|
|
|
|
macro alloc_try(Allocator* allocator, $Type) @nodiscard
|
|
{
|
|
return ($Type*)malloc_try(allocator, $Type.sizeof);
|
|
}
|
|
|
|
macro alloc_with_padding(Allocator* allocator, $Type, usz padding) @nodiscard
|
|
{
|
|
return ($Type*)malloc_try(allocator, $Type.sizeof + padding);
|
|
}
|
|
|
|
macro new_array(Allocator* allocator, $Type, usz elements) @nodiscard
|
|
{
|
|
return new_array_try(allocator, $Type, elements)!!;
|
|
}
|
|
|
|
macro new_array_try(Allocator* allocator, $Type, usz elements) @nodiscard
|
|
{
|
|
return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements];
|
|
}
|
|
|
|
macro new_array_aligned(Allocator* allocator, $Type, usz elements) @nodiscard
|
|
{
|
|
return ((Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
|
|
}
|
|
|
|
macro alloc_array(Allocator* allocator, $Type, usz elements) @nodiscard
|
|
{
|
|
return alloc_array_try(allocator, $Type, elements)!!;
|
|
}
|
|
|
|
macro alloc_array_aligned(Allocator* allocator, $Type, usz elements) @nodiscard
|
|
{
|
|
return ((Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
|
|
}
|
|
|
|
macro alloc_array_try(Allocator* allocator, $Type, usz elements) @nodiscard
|
|
{
|
|
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
|
|
}
|
|
|
|
macro clone(Allocator* allocator, value) @nodiscard
|
|
{
|
|
return new(allocator, $typeof(value), value);
|
|
}
|
|
|
|
fn any* clone_any(Allocator* allocator, any* value) @nodiscard
|
|
{
|
|
usz size = value.type.sizeof;
|
|
void* data = malloc(allocator, size);
|
|
mem::copy(data, value.ptr, size);
|
|
return any_make(data, value.type);
|
|
}
|
|
|
|
// Allocator "functions"
|
|
|
|
macro void*! Allocator.alloc_checked(&self, usz size) @deprecated("Use allocator::malloc_try")
|
|
{
|
|
return malloc_try(self, size);
|
|
}
|
|
|
|
macro void*! Allocator.calloc_checked(&self, usz size) @deprecated("Use allocator::calloc_try")
|
|
{
|
|
return calloc_try(self, size);
|
|
}
|
|
|
|
macro void*! Allocator.realloc_checked(&self, void* ptr, usz new_size) @deprecated("Use allocator::realloc_try")
|
|
{
|
|
return realloc_try(ptr, new_size);
|
|
}
|
|
|
|
macro Allocator.new_array(&self, $Type, usz size, usz end_padding = 0) @deprecated("Use allocator::alloc_array")
|
|
{
|
|
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size]!!;
|
|
}
|
|
|
|
macro Allocator.new_array_checked(&self, $Type, usz size, usz end_padding = 0) @deprecated("Use allocator::alloc_array_try")
|
|
{
|
|
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size];
|
|
}
|
|
|
|
macro Allocator.new_zero_array(&self, $Type, usz size, usz end_padding = 0) @deprecated("Use allocator::new_array")
|
|
{
|
|
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size]!!;
|
|
}
|
|
|
|
macro Allocator.new_zero_array_checked(&self, $Type, usz size, usz end_padding = 0) @deprecated("Use allocator::new_array_try")
|
|
{
|
|
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size];
|
|
}
|
|
|
|
macro Allocator.new(&self, $Type, usz end_padding = 0) @nodiscard @deprecated("Use allocator::alloc")
|
|
{
|
|
return ($Type*)self.alloc_checked($Type.sizeof + end_padding)!!;
|
|
}
|
|
|
|
macro Allocator.new_checked(&self, $Type, usz end_padding = 0) @nodiscard @deprecated("Use allocator::alloc_try")
|
|
{
|
|
return ($Type*)self.alloc_checked($Type.sizeof + end_padding);
|
|
}
|
|
|
|
macro Allocator.new_clear(&self, $Type, usz end_padding = 0) @nodiscard @deprecated("Use allocator::new")
|
|
{
|
|
return ($Type*)self.calloc_checked($Type.sizeof + end_padding)!!;
|
|
}
|
|
|
|
macro Allocator.new_clear_checked(&self, $Type, usz end_padding = 0) @nodiscard @deprecated("Use allocator::new_try")
|
|
{
|
|
return ($Type*)self.calloc_checked($Type.sizeof + end_padding);
|
|
}
|
|
macro Allocator.clone(&self, value) @deprecated("Use allocator::clone")
|
|
{
|
|
var x = self.alloc($typeof(value));
|
|
*x = value;
|
|
return x;
|
|
}
|
|
|
|
fn void* Allocator.alloc(&self, usz size) @nodiscard @deprecated("Use allocator::malloc")
|
|
{
|
|
return malloc(self, size);
|
|
}
|
|
fn void* Allocator.calloc(&self, usz size) @nodiscard @deprecated("Use allocator::calloc")
|
|
{
|
|
return calloc(self, size);
|
|
}
|
|
fn void* Allocator.realloc(&self, void* ptr, usz new_size) @nodiscard @deprecated("Use allocator::realloc")
|
|
{
|
|
return realloc(self, ptr, new_size);
|
|
}
|
|
|
|
fn void*! Allocator.alloc_aligned(&self, usz size, usz alignment, usz offset = 0) @deprecated("Use allocator::malloc_aligned")
|
|
{
|
|
return malloc_aligned(self, size, alignment, 0);
|
|
}
|
|
|
|
fn void*! Allocator.calloc_aligned(&self, usz size, usz alignment, usz offset = 0) @deprecated("Use allocator::calloc_aligned")
|
|
{
|
|
return calloc_aligned(self, size, alignment, 0);
|
|
}
|
|
|
|
fn void*! Allocator.realloc_aligned(&self, void* ptr, usz new_size, usz alignment = 0, usz offset = 0) @deprecated("Use allocator::realloc_aligned")
|
|
{
|
|
return realloc_aligned(self, ptr, new_size, alignment, 0);
|
|
}
|
|
|
|
fn void Allocator.free(&self, void* ptr) @deprecated("Use allocator::free")
|
|
{
|
|
free(self, ptr);
|
|
}
|
|
fn void Allocator.free_aligned(&self, void* ptr) @deprecated("Use allocator::free_aligned")
|
|
{
|
|
free_aligned(self, ptr);
|
|
}
|
|
|
|
|
|
/**
|
|
* @require bytes > 0
|
|
* @require alignment > 0
|
|
* @require bytes <= isz.max
|
|
**/
|
|
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
|
{
|
|
if (alignment < void*.alignof) alignment = void*.alignof;
|
|
usz header = AlignedBlock.sizeof + alignment;
|
|
usz alignsize = bytes + header;
|
|
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
|
|
void* data = #alloc_fn(alignsize)!;
|
|
$else
|
|
void* data = #alloc_fn(alignsize);
|
|
$endif
|
|
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
|
|
AlignedBlock* desc = (AlignedBlock*)mem - 1;
|
|
assert(mem > data);
|
|
*desc = { bytes, data };
|
|
return mem;
|
|
}
|
|
|
|
struct AlignedBlock
|
|
{
|
|
usz len;
|
|
void* start;
|
|
}
|
|
|
|
macro void! @aligned_free(#free_fn, void* old_pointer)
|
|
{
|
|
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
|
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
|
#free_fn(desc.start)!;
|
|
$else
|
|
#free_fn(desc.start);
|
|
$endif
|
|
}
|
|
|
|
/**
|
|
* @require bytes > 0
|
|
* @require alignment > 0
|
|
**/
|
|
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
|
|
{
|
|
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
|
void* data_start = desc.start;
|
|
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
|
|
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
|
|
$if @typekind(#free_fn(data_start)) == OPTIONAL:
|
|
#free_fn(data_start)!;
|
|
$else
|
|
#free_fn(data_start);
|
|
$endif
|
|
return new_data;
|
|
}
|
|
|
|
|
|
// All allocators
|
|
tlocal Allocator* thread_allocator @private = &allocator::LIBC_ALLOCATOR;
|
|
tlocal TempAllocator* thread_temp_allocator @private = null;
|
|
tlocal TempAllocator*[2] temp_allocator_pair @private;
|
|
Allocator* temp_base_allocator @private = &allocator::LIBC_ALLOCATOR;
|
|
|
|
macro TempAllocator* create_default_sized_temp_allocator(Allocator* allocator) @local
|
|
{
|
|
$switch (env::MEMORY_ENV)
|
|
$case NORMAL:
|
|
return new_temp_allocator(1024 * 256, allocator)!!;
|
|
$case SMALL:
|
|
return new_temp_allocator(1024 * 16, allocator)!!;
|
|
$case TINY:
|
|
return new_temp_allocator(1024 * 2, allocator)!!;
|
|
$case NONE:
|
|
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
|
|
$endswitch
|
|
}
|
|
|
|
macro Allocator* heap() => thread_allocator;
|
|
|
|
macro TempAllocator* temp()
|
|
{
|
|
if (!thread_temp_allocator)
|
|
{
|
|
init_default_temp_allocators();
|
|
}
|
|
return thread_temp_allocator;
|
|
}
|
|
|
|
fn void init_default_temp_allocators() @private
|
|
{
|
|
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
|
|
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
|
|
thread_temp_allocator = temp_allocator_pair[0];
|
|
}
|
|
|
|
fn TempAllocator *temp_allocator_next() @private
|
|
{
|
|
if (!thread_temp_allocator)
|
|
{
|
|
init_default_temp_allocators();
|
|
return thread_temp_allocator;
|
|
}
|
|
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
|
|
return thread_temp_allocator = temp_allocator_pair[index];
|
|
} |