diff --git a/code2/grime/allocator_interface.odin b/code2/grime/allocator_interface.odin index 8ad85ec..a38c761 100644 --- a/code2/grime/allocator_interface.odin +++ b/code2/grime/allocator_interface.odin @@ -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) } diff --git a/code2/grime/dynamic_array.odin b/code2/grime/dynamic_array.odin index 749bd94..6c6f7a8 100644 --- a/code2/grime/dynamic_array.odin +++ b/code2/grime/dynamic_array.odin @@ -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 -} \ No newline at end of file +} diff --git a/code2/grime/farena.odin b/code2/grime/farena.odin index eb31fb6..b31b54f 100644 --- a/code2/grime/farena.odin +++ b/code2/grime/farena.odin @@ -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} } diff --git a/code2/grime/key_table_1_layer_chained_chunked_cells.odin b/code2/grime/key_table_1_layer_chained_chunked_cells.odin index 9420d84..88d05c3 100644 --- a/code2/grime/key_table_1_layer_chained_chunked_cells.odin +++ b/code2/grime/key_table_1_layer_chained_chunked_cells.odin @@ -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 diff --git a/code2/grime/memory.odin b/code2/grime/memory.odin index 03ee8a3..0baceff 100644 --- a/code2/grime/memory.odin +++ b/code2/grime/memory.odin @@ -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. diff --git a/code2/grime/pkg_mappings.odin b/code2/grime/pkg_mappings.odin index 34df0f2..ca1be2c 100644 --- a/code2/grime/pkg_mappings.odin +++ b/code2/grime/pkg_mappings.odin @@ -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 { diff --git a/code2/grime/stirngs.odin b/code2/grime/stirngs.odin index a1cc419..fabcb1a 100644 --- a/code2/grime/stirngs.odin +++ b/code2/grime/stirngs.odin @@ -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) }