progress on grime
This commit is contained in:
@@ -1,10 +1,31 @@
|
||||
package grime
|
||||
|
||||
/*
|
||||
This is an non-ideomatic allocator interface inspired Odin/Jai/gb/zpl-c.
|
||||
This is an non-ideomatic allocator interface inspired by Odin/Jai/gb/zpl-c.
|
||||
|
||||
By the default the interface is still compatible Odin's context system however the user is expected to wrap the allocator struct with odin_ainfo_wrap to ideomatic procedures.
|
||||
For details see: Ideomatic Compatability Wrapper (just search it)
|
||||
|
||||
For debug builds, we do not directly do calls to a procedure in the codebase's code paths, instead we pass a proc id that we resolve on interface calls.
|
||||
This allows for hot-reload without needing to patch persistent allocator references.
|
||||
|
||||
To support what ideomatic odin expects in their respective codepaths, all of of sectr's codebase package mappings will wrap procedures in the formaat:
|
||||
alias_symbol :: #force_inline proc ... (..., allocator := context.allocator) { return thidparty_symbol(..., allocator = resolve_odin_allocator(allocator)) }
|
||||
- or
|
||||
alias_symbol :: #force_inline proc ... (..., allocator := context.temp_allocator) { return thidparty_symbol(..., allocator = resolve_odin_allocator(allocator)) }
|
||||
- or
|
||||
alias_symbol :: #force_inline proc ... (...) {
|
||||
context.allocator = resolve_odin_allocator(context.allocator)
|
||||
context.temp_allocator = resolve_odin_allocator(context.temp_allocator)
|
||||
return thidparty_symbol(..., allocator = resolve_odin_allocator(allocator)) }
|
||||
}
|
||||
|
||||
resolve_odin_allocator: Will procedue an Allocator struct with the procedure mapping resolved.
|
||||
resolve_allocator_proc: Used for the personalized interface to resolve the mapping right on call.
|
||||
|
||||
It "problably" is possible to extend the original allocator interface without modifying the original source so that the distinction between the codebase's
|
||||
generic allocator interface at least converges to use the same proc signature. However since the package mapping symbols are already needing the resolve call
|
||||
for the patchless hot-reload, the cost is just two procs for each interface.
|
||||
*/
|
||||
|
||||
AllocatorOp :: enum u32 {
|
||||
@@ -51,7 +72,7 @@ AllocatorSP :: struct {
|
||||
type_sig: AllocatorProc,
|
||||
slot: int,
|
||||
}
|
||||
AllocatorProc :: #type proc (input: AllocatorProc_In, out: ^AllocatorProc_Out)
|
||||
AllocatorProc :: #type proc(input: AllocatorProc_In, out: ^AllocatorProc_Out)
|
||||
AllocatorProc_In :: struct {
|
||||
data: rawptr,
|
||||
requested_size: int,
|
||||
@@ -61,6 +82,7 @@ AllocatorProc_In :: struct {
|
||||
save_point : AllocatorSP,
|
||||
},
|
||||
op: AllocatorOp,
|
||||
loc: SourceCodeLocation,
|
||||
}
|
||||
AllocatorProc_Out :: struct {
|
||||
using _ : struct #raw_union {
|
||||
@@ -86,157 +108,60 @@ AllocatorInfo :: struct {
|
||||
procedure: AllocatorProc,
|
||||
proc_id: AllocatorProcID,
|
||||
},
|
||||
data: rawptr,
|
||||
data: rawptr,
|
||||
}
|
||||
// #assert(size_of(AllocatorQueryInfo) == size_of(AllocatorProc_Out))
|
||||
|
||||
// Listing of every single allocator (used on hot-reloadable builds)
|
||||
AllocatorProcID :: enum uintptr {
|
||||
FArena,
|
||||
VArena,
|
||||
CArena,
|
||||
Pool,
|
||||
Slab,
|
||||
Odin_Arena,
|
||||
// VArena,
|
||||
// CArena,
|
||||
// Pool,
|
||||
// Slab,
|
||||
// Odin_Arena,
|
||||
// Odin_VArena,
|
||||
}
|
||||
|
||||
resolve_allocator_proc :: #force_inline proc(procedure: $AllocatorProcType) -> AllocatorProc {
|
||||
resolve_allocator_proc :: #force_inline proc "contextless" (procedure: $AllocatorProcType) -> AllocatorProc {
|
||||
when ODIN_DEBUG {
|
||||
switch (transmute(AllocatorProcID)procedure) {
|
||||
case .FArena: return farena_allocator_proc
|
||||
case .VArena: return nil // varena_allocaotr_proc
|
||||
case .CArena: return nil // carena_allocator_proc
|
||||
case .Pool: return nil // pool_allocator_proc
|
||||
case .Slab: return nil // slab_allocator_proc
|
||||
case .Odin_Arena: return nil // odin_arena_allocator_proc
|
||||
// case .VArena: return varena_allocaotr_proc
|
||||
// case .CArena: return carena_allocator_proc
|
||||
// case .Pool: return pool_allocator_proc
|
||||
// case .Slab: return slab_allocator_proc
|
||||
// case .Odin_Arena: return odin_arena_allocator_proc
|
||||
// case .Odin_VArena: return odin_varena_allocator_proc
|
||||
}
|
||||
}
|
||||
else {
|
||||
return transmute(AllocatorProc) procedure
|
||||
}
|
||||
return nil
|
||||
panic_contextless("Unresolvable procedure")
|
||||
}
|
||||
|
||||
MEMORY_ALIGNMENT_DEFAULT :: 2 * size_of(rawptr)
|
||||
|
||||
ainfo :: #force_inline proc(ainfo := context.allocator) -> AllocatorInfo { return transmute(AllocatorInfo) ainfo }
|
||||
odin_allocator :: #force_inline proc(ainfo: AllocatorInfo) -> Odin_Allocator { return transmute(Odin_Allocator) ainfo }
|
||||
|
||||
allocator_query :: proc(ainfo := context.allocator) -> AllocatorQueryInfo {
|
||||
assert(ainfo.procedure != nil)
|
||||
out: AllocatorQueryInfo; resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Query}, transmute(^AllocatorProc_Out) & out)
|
||||
return out
|
||||
}
|
||||
mem_free_ainfo :: proc(mem: []byte, ainfo: AllocatorInfo) {
|
||||
assert(ainfo.procedure != nil)
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Free, old_allocation = mem}, & {})
|
||||
}
|
||||
mem_reset :: proc(ainfo := context.allocator) {
|
||||
assert(ainfo.procedure != nil)
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Reset}, &{})
|
||||
}
|
||||
mem_rewind :: proc(ainfo := context.allocator, save_point: AllocatorSP) {
|
||||
assert(ainfo.procedure != nil)
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Rewind, save_point = save_point}, & {})
|
||||
}
|
||||
mem_save_point :: proc(ainfo := context.allocator) -> AllocatorSP {
|
||||
assert(ainfo.procedure != nil)
|
||||
out: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .SavePoint}, & out)
|
||||
return out.save_point
|
||||
}
|
||||
mem_alloc :: proc(size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo : $Type = context.allocator) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Alloc_NoZero : .Alloc,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
resolve_odin_allocator :: #force_inline proc "contextless" (allocator: Odin_Allocator) -> Odin_Allocator {
|
||||
when ODIN_DEBUG {
|
||||
switch (transmute(AllocatorProcID)allocator.procedure) {
|
||||
case .FArena: return { farena_odin_allocator_proc, allocator.data }
|
||||
// case .VArena: return { varena_odin_allocaotr_proc, allocator.data }
|
||||
// case .CArena: return { carena_odin_allocator_proc, allocator.data }
|
||||
// case .Pool: return nil // pool_allocator_proc
|
||||
// case .Slab: return nil // slab_allocator_proc
|
||||
// case .Odin_Arena: return nil // odin_arena_allocator_proc
|
||||
// case .Odin_VArena: return odin_varena_allocator_proc
|
||||
}
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
}
|
||||
mem_grow :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Grow_NoZero : .Grow,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
old_allocation = mem,
|
||||
else {
|
||||
switch (allocator.procedure) {
|
||||
case farena_allocator_proc: return { farena_odin_allocator_proc, allocator.data }
|
||||
case varena_allocator_proc: return { varena_odin_allocator_proc, allocator.data }
|
||||
case carena_allocator_proc: return { carena_odin_allocator_proc, allocator.data }
|
||||
}
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
panic_contextless("Unresolvable procedure")
|
||||
}
|
||||
mem_resize :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = len(mem) < size ? .Shrink : no_zero ? .Grow_NoZero : .Grow,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
old_allocation = mem,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
}
|
||||
mem_shrink :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = .Shrink,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
old_allocation = mem,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
}
|
||||
|
||||
alloc_type :: proc($Type: typeid, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> ^Type {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Alloc_NoZero : .Alloc,
|
||||
requested_size = size_of(Type),
|
||||
alignment = alignment,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return transmute(^Type) raw_data(output.allocation)
|
||||
}
|
||||
alloc_slice :: proc($SliceType: typeid / []$Type, num : int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []Type {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Alloc_NoZero : .Alloc,
|
||||
requested_size = size_of(Type) * num,
|
||||
alignment = alignment,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return transmute([]Type) slice(raw_data(output.allocation), num)
|
||||
}
|
||||
|
||||
/*
|
||||
Idiomatic Compatability Wrapper
|
||||
|
||||
Ideally we wrap all procedures that go to ideomatic odin with the following pattern:
|
||||
|
||||
Usually we do the following:
|
||||
```
|
||||
import "core:dynlib"
|
||||
os_lib_load :: dynlib.load_library
|
||||
```
|
||||
Instead:
|
||||
os_lib_load :: #force_inline proc "contextless" (... same signature as load_library, allocator := ...) { return dynlib.load_library(..., odin_ainfo_wrap(allocator)) }
|
||||
*/
|
||||
|
||||
odin_allocator_mode_to_allocator_op :: #force_inline proc "contextless" (mode: Odin_AllocatorMode, size_diff : int) -> AllocatorOp {
|
||||
switch mode {
|
||||
@@ -252,45 +177,113 @@ odin_allocator_mode_to_allocator_op :: #force_inline proc "contextless" (mode: O
|
||||
panic_contextless("Impossible path")
|
||||
}
|
||||
|
||||
odin_allocator_wrap_proc :: proc(
|
||||
allocator_data : rawptr,
|
||||
mode : Odin_AllocatorMode,
|
||||
size : int,
|
||||
alignment : int,
|
||||
old_memory : rawptr,
|
||||
old_size : int,
|
||||
loc := #caller_location
|
||||
) -> ( data : []byte, alloc_error : Odin_AllocatorError)
|
||||
{
|
||||
MEMORY_ALIGNMENT_DEFAULT :: 2 * size_of(rawptr)
|
||||
|
||||
allocatorinfo :: #force_inline proc(ainfo := context.allocator) -> AllocatorInfo { return transmute(AllocatorInfo) ainfo }
|
||||
allocator :: #force_inline proc(ainfo: AllocatorInfo) -> Odin_Allocator { return transmute(Odin_Allocator) ainfo }
|
||||
|
||||
allocator_query :: proc(ainfo := context.allocator, loc := #caller_location) -> AllocatorQueryInfo {
|
||||
assert(ainfo.procedure != nil)
|
||||
out: AllocatorQueryInfo; resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Query, loc = loc}, transmute(^AllocatorProc_Out) & out)
|
||||
return out
|
||||
}
|
||||
mem_free_ainfo :: proc(mem: []byte, ainfo: AllocatorInfo, loc := #caller_location) {
|
||||
assert(ainfo.procedure != nil)
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Free, old_allocation = mem, loc = loc}, & {})
|
||||
}
|
||||
mem_reset :: proc(ainfo := context.allocator, loc := #caller_location) {
|
||||
assert(ainfo.procedure != nil)
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Reset, loc = loc}, &{})
|
||||
}
|
||||
mem_rewind :: proc(ainfo := context.allocator, save_point: AllocatorSP, loc := #caller_location) {
|
||||
assert(ainfo.procedure != nil)
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .Rewind, save_point = save_point, loc = loc}, & {})
|
||||
}
|
||||
mem_save_point :: proc(ainfo := context.allocator, loc := #caller_location) -> AllocatorSP {
|
||||
assert(ainfo.procedure != nil)
|
||||
out: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)({data = ainfo.data, op = .SavePoint, loc = loc}, & out)
|
||||
return out.save_point
|
||||
}
|
||||
mem_alloc :: proc(size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo : $Type = context.allocator, loc := #caller_location) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = (transmute(^AllocatorInfo)allocator_data).data,
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Alloc_NoZero : .Alloc,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
old_allocation = slice(transmute([^]byte)old_memory, old_size),
|
||||
op = odin_allocator_mode_to_allocator_op(mode, size - old_size),
|
||||
loc = loc,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc((transmute(^Odin_Allocator)allocator_data).procedure)(input, & output)
|
||||
|
||||
#partial switch mode {
|
||||
case .Query_Features:
|
||||
debug_trap() // TODO(Ed): Finish this...
|
||||
return nil, nil
|
||||
case .Query_Info:
|
||||
info := (^Odin_AllocatorQueryInfo)(old_memory)
|
||||
if info != nil && info.pointer != nil {
|
||||
info.size = output.left
|
||||
info.alignment = cast(int) (transmute(AllocatorQueryInfo)output).alignment
|
||||
return slice(transmute(^byte)info, size_of(info^) ), nil
|
||||
}
|
||||
return nil, nil
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
}
|
||||
mem_grow :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator, loc := #caller_location) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Grow_NoZero : .Grow,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
old_allocation = mem,
|
||||
loc = loc,
|
||||
}
|
||||
return output.allocation, cast(Odin_AllocatorError)output.error
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
}
|
||||
mem_resize :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator, loc := #caller_location) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = len(mem) < size ? .Shrink : no_zero ? .Grow_NoZero : .Grow,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
old_allocation = mem,
|
||||
loc = loc,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
}
|
||||
mem_shrink :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator, loc := #caller_location) -> []byte {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = .Shrink,
|
||||
requested_size = size,
|
||||
alignment = alignment,
|
||||
old_allocation = mem,
|
||||
loc = loc,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return output.allocation
|
||||
}
|
||||
|
||||
odin_ainfo_giftwrap :: #force_inline proc(ainfo := context.allocator) -> Odin_Allocator {
|
||||
@(thread_local)
|
||||
cursed_allocator_wrap_ref : Odin_Allocator
|
||||
cursed_allocator_wrap_ref = {ainfo.procedure, ainfo.data}
|
||||
return {odin_allocator_wrap_proc, & cursed_allocator_wrap_ref}
|
||||
alloc_type :: proc($Type: typeid, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator, loc := #caller_location) -> ^Type {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Alloc_NoZero : .Alloc,
|
||||
requested_size = size_of(Type),
|
||||
alignment = alignment,
|
||||
loc = loc,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return transmute(^Type) raw_data(output.allocation)
|
||||
}
|
||||
alloc_slice :: proc($SliceType: typeid / []$Type, num : int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator, loc := #caller_location) -> []Type {
|
||||
assert(ainfo.procedure != nil)
|
||||
input := AllocatorProc_In {
|
||||
data = ainfo.data,
|
||||
op = no_zero ? .Alloc_NoZero : .Alloc,
|
||||
requested_size = size_of(Type) * num,
|
||||
alignment = alignment,
|
||||
loc = loc,
|
||||
}
|
||||
output: AllocatorProc_Out
|
||||
resolve_allocator_proc(ainfo.procedure)(input, & output)
|
||||
return transmute([]Type) slice(raw_data(output.allocation), num)
|
||||
}
|
||||
|
@@ -14,9 +14,8 @@ I can use either... so I'll just keep both
|
||||
*/
|
||||
|
||||
ArrayHeader :: struct ( $ Type : typeid ) {
|
||||
backing : Odin_Allocator,
|
||||
backing : AllocatorInfo,
|
||||
dbg_name : string,
|
||||
fixed_cap : b32,
|
||||
capacity : int,
|
||||
num : int,
|
||||
data : [^]Type,
|
||||
@@ -60,4 +59,4 @@ array_init :: proc( $Array_Type : typeid/Array($Type), capacity : u64,
|
||||
result.capacity = capacity
|
||||
result.data = cast( [^]Type ) (cast( [^]ArrayHeader(Type)) result.header)[ 1:]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@@ -1,50 +1,52 @@
|
||||
package grime
|
||||
|
||||
/* Fixed Arena Allocator (Fixed-szie block bump allocator) */
|
||||
|
||||
FArena :: struct {
|
||||
mem: []byte,
|
||||
used: int,
|
||||
}
|
||||
@require_results
|
||||
farena_make :: proc(backing: []byte) -> FArena {
|
||||
farena_make :: proc "contextless" (backing: []byte) -> FArena {
|
||||
arena := FArena {mem = backing}
|
||||
return arena
|
||||
}
|
||||
farena_init :: proc(arena: ^FArena, backing: []byte) {
|
||||
assert(arena != nil)
|
||||
farena_init :: proc "contextless" (arena: ^FArena, backing: []byte) {
|
||||
assert_contextless(arena != nil)
|
||||
arena.mem = backing
|
||||
arena.used = 0
|
||||
}
|
||||
@require_results
|
||||
farena_push :: proc(arena: ^FArena, $Type: typeid, amount: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT) -> []Type {
|
||||
assert(arena != nil)
|
||||
farena_push :: proc "contextless" (arena: ^FArena, $Type: typeid, amount: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, loc := #caller_location) -> ([]Type, AllocatorError) {
|
||||
assert_contextless(arena != nil)
|
||||
if amount == 0 {
|
||||
return {}
|
||||
return {}, .None
|
||||
}
|
||||
desired := size_of(Type) * amount
|
||||
to_commit := align_pow2(desired, alignment)
|
||||
unused := len(arena.mem) - arena.used
|
||||
assert(to_commit <= unused)
|
||||
ptr := cursor(arena.mem[arena.used:])
|
||||
if (to_commit <= unused) {
|
||||
return {}, .Out_Of_Memory
|
||||
}
|
||||
arena.used += to_commit
|
||||
return slice(ptr, amount)
|
||||
return slice(cursor(arena.mem)[arena.used:], amount), .None
|
||||
}
|
||||
@require_results
|
||||
farena_grow :: proc(arena: ^FArena, old_allocation: []byte, requested_size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, should_zero: bool = true) -> (allocation: []byte, err: AllocatorError) {
|
||||
farena_grow :: proc "contextless" (arena: ^FArena, old_allocation: []byte, requested_size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, should_zero: bool = true, loc := #caller_location) -> (allocation: []byte, err: AllocatorError) {
|
||||
assert_contextless(arena != nil)
|
||||
if len(old_allocation) == 0 {
|
||||
return {}, .Invalid_Argument
|
||||
}
|
||||
alloc_end := end(old_allocation)
|
||||
arena_end := cursor(arena.mem)[arena.used:]
|
||||
if alloc_end != arena_end {
|
||||
// Not at the end, can't grow in place
|
||||
if alloc_end != arena_end {
|
||||
return {}, .Out_Of_Memory
|
||||
}
|
||||
// Calculate growth
|
||||
grow_amount := requested_size - len(old_allocation)
|
||||
aligned_grow := align_pow2(grow_amount, alignment)
|
||||
unused := len(arena.mem) - arena.used
|
||||
if aligned_grow > unused {
|
||||
// Not enough space
|
||||
if aligned_grow > unused {
|
||||
return {}, .Out_Of_Memory
|
||||
}
|
||||
arena.used += aligned_grow
|
||||
@@ -56,7 +58,8 @@ farena_grow :: proc(arena: ^FArena, old_allocation: []byte, requested_size: int,
|
||||
return
|
||||
}
|
||||
@require_results
|
||||
farena_shirnk :: proc(arena: ^FArena, old_allocation: []byte, requested_size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT) -> (allocation: []byte, err: AllocatorError) {
|
||||
farena_shirnk :: proc "contextless" (arena: ^FArena, old_allocation: []byte, requested_size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, loc := #caller_location) -> (allocation: []byte, err: AllocatorError) {
|
||||
assert_contextless(arena != nil)
|
||||
if len(old_allocation) == 0 {
|
||||
return {}, .Invalid_Argument
|
||||
}
|
||||
@@ -64,56 +67,105 @@ farena_shirnk :: proc(arena: ^FArena, old_allocation: []byte, requested_size: in
|
||||
arena_end := cursor(arena.mem)[arena.used:]
|
||||
if alloc_end != arena_end {
|
||||
// Not at the end, can't shrink but return adjusted size
|
||||
allocation = old_allocation[:requested_size]
|
||||
err = .None
|
||||
return
|
||||
return old_allocation[:requested_size], .None
|
||||
}
|
||||
// Calculate shrinkage
|
||||
aligned_original := align_pow2(len(old_allocation), MEMORY_ALIGNMENT_DEFAULT)
|
||||
aligned_new := align_pow2(requested_size, alignment)
|
||||
arena.used -= (aligned_original - aligned_new)
|
||||
allocation = old_allocation[:requested_size]
|
||||
return
|
||||
return old_allocation[:requested_size], .None
|
||||
}
|
||||
farena_reset :: proc(arena: ^FArena) {
|
||||
farena_reset :: #force_inline proc "contextless" (arena: ^FArena, loc := #caller_location) {
|
||||
assert_contextless(arena != nil)
|
||||
arena.used = 0
|
||||
}
|
||||
farena_rewind :: proc(arena: ^FArena, save_point: AllocatorSP) {
|
||||
assert(save_point.type_sig == farena_allocator_proc)
|
||||
assert(save_point.slot >= 0 && save_point.slot <= arena.used)
|
||||
farena_rewind :: #force_inline proc "contextless" (arena: ^FArena, save_point: AllocatorSP, loc := #caller_location) {
|
||||
assert_contextless(save_point.type_sig == farena_allocator_proc)
|
||||
assert_contextless(save_point.slot >= 0 && save_point.slot <= arena.used)
|
||||
arena.used = save_point.slot
|
||||
}
|
||||
farena_save :: #force_inline proc(arena: FArena) -> AllocatorSP { return AllocatorSP { type_sig = farena_allocator_proc, slot = arena.used } }
|
||||
farena_save :: #force_inline proc "contextless" (arena: FArena) -> AllocatorSP { return AllocatorSP { type_sig = farena_allocator_proc, slot = arena.used } }
|
||||
farena_allocator_proc :: proc(input: AllocatorProc_In, output: ^AllocatorProc_Out) {
|
||||
assert(output != nil)
|
||||
assert(input.data != nil)
|
||||
assert_contextless(output != nil)
|
||||
assert_contextless(input.data != nil)
|
||||
arena := transmute(^FArena) input.data
|
||||
switch input.op
|
||||
{
|
||||
switch input.op {
|
||||
case .Alloc, .Alloc_NoZero:
|
||||
output.allocation = to_bytes(farena_push(arena, byte, input.requested_size, input.alignment))
|
||||
output.allocation, output.error = farena_push(arena, byte, input.requested_size, input.alignment, input.loc)
|
||||
if input.op == .Alloc {
|
||||
zero(output.allocation)
|
||||
}
|
||||
return
|
||||
case .Free:
|
||||
// No-op for arena
|
||||
return
|
||||
case .Reset:
|
||||
farena_reset(arena)
|
||||
return
|
||||
case .Grow, .Grow_NoZero:
|
||||
output.allocation, output.error = farena_grow(arena, input.old_allocation, input.requested_size, input.alignment, input.op == .Grow)
|
||||
return
|
||||
case .Shrink:
|
||||
output.allocation, output.error = farena_shirnk(arena, input.old_allocation, input.requested_size, input.alignment)
|
||||
return
|
||||
case .Rewind:
|
||||
farena_rewind(arena, input.save_point)
|
||||
return
|
||||
case .SavePoint:
|
||||
output.save_point = farena_save(arena^)
|
||||
return
|
||||
case .Query:
|
||||
output.features = {.Alloc, .Reset, .Grow, .Shrink, .Rewind}
|
||||
output.max_alloc = len(arena.mem) - arena.used
|
||||
output.min_alloc = 0
|
||||
output.left = output.max_alloc
|
||||
output.save_point = farena_save(arena^)
|
||||
return
|
||||
}
|
||||
panic_contextless("Impossible path")
|
||||
}
|
||||
farena_odin_allocator_proc :: proc(
|
||||
allocator_data : rawptr,
|
||||
mode : Odin_AllocatorMode,
|
||||
size : int,
|
||||
alignment : int,
|
||||
old_memory : rawptr,
|
||||
old_size : int,
|
||||
location : SourceCodeLocation = #caller_location
|
||||
) -> ( data : []byte, alloc_error : AllocatorError)
|
||||
{
|
||||
assert_contextless(allocator_data != nil)
|
||||
arena := transmute(^FArena) allocator_data
|
||||
switch mode {
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
data, alloc_error = farena_push(arena, byte, size, alignment, location)
|
||||
if mode == .Alloc {
|
||||
zero(data)
|
||||
}
|
||||
return
|
||||
case .Free:
|
||||
return {}, .Mode_Not_Implemented
|
||||
case .Free_All:
|
||||
farena_reset(arena)
|
||||
return
|
||||
case .Resize, .Resize_Non_Zeroed:
|
||||
if (size > old_size) do data, alloc_error = farena_grow (arena, slice(cursor(old_memory), old_size), size, alignment, mode == .Resize)
|
||||
else do data, alloc_error = farena_shirnk(arena, slice(cursor(old_memory), old_size), size, alignment)
|
||||
return
|
||||
case .Query_Features:
|
||||
set := (^Odin_AllocatorModeSet)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features, .Query_Info}
|
||||
}
|
||||
return
|
||||
case .Query_Info:
|
||||
info := (^Odin_AllocatorQueryInfo)(old_memory)
|
||||
info.pointer = transmute(rawptr) farena_save(arena^).slot
|
||||
info.size = len(arena.mem) - arena.used
|
||||
info.alignment = MEMORY_ALIGNMENT_DEFAULT
|
||||
return to_bytes(info), nil
|
||||
}
|
||||
panic_contextless("Impossible path")
|
||||
}
|
||||
when ODIN_DEBUG {
|
||||
farena_ainfo :: #force_inline proc "contextless" (arena: ^FArena) -> AllocatorInfo { return AllocatorInfo{proc_id = .FArena, data = arena} }
|
||||
|
@@ -56,7 +56,7 @@ kt1cx_init :: proc(info: KT1CX_Info, m: KT1CX_InfoMeta, result: ^KT1CX_Byte) {
|
||||
assert(m.cell_depth > 0)
|
||||
assert(m.table_size >= 4 * Kilo)
|
||||
assert(m.type_width > 0)
|
||||
table_raw := transmute(SliceByte) mem_alloc(m.table_size * m.cell_size, ainfo = odin_allocator(info.backing_table))
|
||||
table_raw := transmute(SliceByte) mem_alloc(m.table_size * m.cell_size, ainfo = allocator(info.backing_table))
|
||||
slice_assert(transmute([]byte) table_raw)
|
||||
table_raw.len = m.table_size
|
||||
result.table = transmute([]byte) table_raw
|
||||
@@ -145,7 +145,7 @@ kt1cx_set :: proc(kt: KT1CX_Byte, key: u64, value: []byte, backing_cells: Alloca
|
||||
continue
|
||||
}
|
||||
else {
|
||||
new_cell := mem_alloc(m.cell_size, ainfo = odin_allocator(backing_cells))
|
||||
new_cell := mem_alloc(m.cell_size, ainfo = allocator(backing_cells))
|
||||
curr_cell.next = raw_data(new_cell)
|
||||
slot = transmute(^KT1CX_Byte_Slot) cursor(new_cell)[m.slot_key_offset:]
|
||||
slot.occupied = true
|
||||
|
@@ -5,8 +5,16 @@ Mega :: Kilo * 1024
|
||||
Giga :: Mega * 1024
|
||||
Tera :: Giga * 1024
|
||||
|
||||
raw_cursor :: #force_inline proc "contextless" (ptr: rawptr) -> [^]byte { return transmute([^]byte) ptr }
|
||||
ptr_cursor :: #force_inline proc "contextless" (ptr: ^$Type) -> [^]Type { return transmute([^]Type) ptr }
|
||||
|
||||
@(require_results) is_power_of_two :: #force_inline proc "contextless" (x: uintptr) -> bool { return (x > 0) && ((x & (x-1)) == 0) }
|
||||
@(require_results)
|
||||
align_pow2 :: #force_inline proc "contextless" (ptr, align: int) -> int {
|
||||
assert_contextless(is_power_of_two(uintptr(align)))
|
||||
return ptr & ~(align-1)
|
||||
}
|
||||
|
||||
memory_zero_explicit :: #force_inline proc "contextless" (data: rawptr, len: int) -> rawptr {
|
||||
mem_zero_volatile(data, len) // Use the volatile mem_zero
|
||||
atomic_thread_fence(.Seq_Cst) // Prevent reordering
|
||||
@@ -23,9 +31,9 @@ SliceRaw :: struct ($Type: typeid) {
|
||||
}
|
||||
slice :: #force_inline proc "contextless" (s: [^] $Type, num: $Some_Integer) -> [ ]Type { return transmute([]Type) SliceRaw(Type) { s, cast(int) num } }
|
||||
slice_cursor :: #force_inline proc "contextless" (s: []$Type) -> [^]Type { return transmute([^]Type) raw_data(s) }
|
||||
slice_assert :: #force_inline proc (s: $SliceType / []$Type) {
|
||||
assert(len(s) > 0)
|
||||
assert(s != nil)
|
||||
slice_assert :: #force_inline proc "contextless" (s: $SliceType / []$Type) {
|
||||
assert_contextless(len(s) > 0)
|
||||
assert_contextless(s != nil)
|
||||
}
|
||||
slice_end :: #force_inline proc "contextless" (s : $SliceType / []$Type) -> ^Type { return cursor(s)[len(s):] }
|
||||
slice_byte_end :: #force_inline proc "contextless" (s : SliceByte) -> ^byte { return s.data[s.len:] }
|
||||
@@ -41,6 +49,8 @@ slice_copy :: #force_inline proc "contextless" (dst, src: $SliceType / []$Type)
|
||||
@(require_results) slice_to_bytes :: #force_inline proc "contextless" (s: []$Type) -> []byte { return ([^]byte)(raw_data(s))[:len(s) * size_of(Type)] }
|
||||
@(require_results) slice_raw :: #force_inline proc "contextless" (s: []$Type) -> SliceRaw(Type) { return transmute(SliceRaw(Type)) s }
|
||||
|
||||
@(require_results) type_to_bytes :: #force_inline proc "contextless" (obj: ^$Type) -> []byte { return ([^]byte)(obj)[:size_of(Type)] }
|
||||
|
||||
//region Memory Math
|
||||
|
||||
// See: core/mem.odin, I wanted to study it an didn't like the naming.
|
||||
|
@@ -21,13 +21,14 @@ import "base:runtime"
|
||||
LoggerLevel :: runtime.Logger_Level
|
||||
LoggerOptions :: runtime.Logger_Options
|
||||
Random_Generator :: runtime.Random_Generator
|
||||
SourceCodeLocation :: runtime.Source_Code_Location
|
||||
slice_copy_overlapping :: runtime.copy_slice
|
||||
|
||||
import fmt_io "core:fmt"
|
||||
// % based template formatters
|
||||
str_pfmt_out :: fmt_io.printf
|
||||
str_pfmt_tmp :: #force_inline proc(fmt: string, args: ..any, newline := false) -> string { context.temp_allocator = odin_ainfo_giftwrap(context.temp_allocator); return fmt_io.tprintf(fmt, ..args, newline = newline) }
|
||||
str_pfmt :: fmt_io.aprintf // Decided to make aprintf the default. (It will always be the default allocator)
|
||||
str_pfmt_tmp :: #force_inline proc(fmt: string, args: ..any, newline := false) -> string { context.temp_allocator = resolve_odin_allocator(context.temp_allocator); return fmt_io.tprintf(fmt, ..args, newline = newline) }
|
||||
str_pfmt :: #force_inline proc(fmt: string, args: ..any, allocator := context.allocator, newline := false) -> string { return fmt_io.aprintf(fmt, ..args, newline = newline, allocator = resolve_odin_allocator(allocator)) }
|
||||
str_pfmt_builder :: fmt_io.sbprintf
|
||||
str_pfmt_buffer :: fmt_io.bprintf
|
||||
str_pfmt_file_ln :: fmt_io.fprintln
|
||||
@@ -38,18 +39,16 @@ import "core:log"
|
||||
Logger_Full_Timestamp_Opts :: log.Full_Timestamp_Opts
|
||||
|
||||
import "core:mem"
|
||||
Odin_AllocatorMode :: mem.Allocator_Mode
|
||||
Odin_AllocatorProc :: mem.Allocator_Proc
|
||||
Odin_Allocator :: mem.Allocator
|
||||
Odin_AllocatorQueryInfo :: mem.Allocator_Query_Info
|
||||
Odin_AllocatorError :: mem.Allocator_Error
|
||||
Odin_AllocatorQueryInfo :: mem.Allocator_Query_Info
|
||||
Odin_AllocatorMode :: mem.Allocator_Mode
|
||||
Odin_AllocatorModeSet :: mem.Allocator_Mode_Set
|
||||
Odin_AllocatorProc :: mem.Allocator_Proc
|
||||
|
||||
align_forward_int :: mem.align_forward_int
|
||||
align_forward_uintptr :: mem.align_backward_uintptr
|
||||
align_forward_raw :: mem.align_forward
|
||||
is_power_of_two :: mem.is_power_of_two
|
||||
|
||||
align_pow2 :: mem.align_forward_int
|
||||
|
||||
import "core:mem/virtual"
|
||||
VirtualProtectFlags :: virtual.Protect_Flags
|
||||
@@ -99,6 +98,8 @@ import "core:unicode/utf8"
|
||||
// string_to_runes :: utf8.string_to_runes
|
||||
|
||||
cursor :: proc {
|
||||
raw_cursor,
|
||||
ptr_cursor,
|
||||
slice_cursor,
|
||||
string_cursor,
|
||||
}
|
||||
@@ -125,6 +126,7 @@ copy_non_overlaping :: proc {
|
||||
|
||||
to_bytes :: proc {
|
||||
slice_to_bytes,
|
||||
type_to_bytes,
|
||||
}
|
||||
|
||||
zero :: proc {
|
||||
|
@@ -4,7 +4,7 @@ Raw_String :: struct {
|
||||
data: [^]byte,
|
||||
len: int,
|
||||
}
|
||||
string_cursor :: proc(s: string) -> [^]u8 { return slice_cursor(transmute([]byte) s) }
|
||||
string_copy :: proc(dst, src: string) { slice_copy (transmute([]byte) dst, transmute([]byte) src) }
|
||||
string_end :: proc(s: string) -> ^u8 { return slice_end (transmute([]byte) s) }
|
||||
string_assert :: proc(s: string) { slice_assert(transmute([]byte) s) }
|
||||
string_cursor :: #force_inline proc "contextless" (s: string) -> [^]u8 { return slice_cursor(transmute([]byte) s) }
|
||||
string_copy :: #force_inline proc "contextless" (dst, src: string) { slice_copy (transmute([]byte) dst, transmute([]byte) src) }
|
||||
string_end :: #force_inline proc "contextless" (s: string) -> ^u8 { return slice_end (transmute([]byte) s) }
|
||||
string_assert :: #force_inline proc "contextless" (s: string) { slice_assert(transmute([]byte) s) }
|
||||
|
Reference in New Issue
Block a user