progress on grime

This commit is contained in:
2025-10-12 16:19:26 -04:00
parent 80846d035f
commit 866432723e
7 changed files with 269 additions and 213 deletions

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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} }

View File

@@ -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

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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) }