384 lines
10 KiB
Text
384 lines
10 KiB
Text
#add_context arena := Arena.{ proc = PagingArenaProc };
|
|
#add_context temp_arena := Arena.{ proc = TempArenaProc };
|
|
|
|
/// Arena is an interface for an arena-based memory allocation.
|
|
Arena :: struct {
|
|
proc: ArenaProc;
|
|
data: *void;
|
|
}
|
|
|
|
ArenaProc :: #type (
|
|
event: ArenaEvent,
|
|
arena_data: *void,
|
|
|
|
new_size: int, old_size: int, align: int,
|
|
new_ptr: *void, old_ptr: *void,
|
|
|
|
caller: Source_Code_Location
|
|
) -> *void;
|
|
|
|
ArenaEvent :: enum {
|
|
Setup;
|
|
Teardown;
|
|
|
|
Acquire;
|
|
Reset;
|
|
|
|
Save;
|
|
Restore;
|
|
}
|
|
|
|
/// ArenaPush pushes a value of type T to the current context.arena.
|
|
ArenaPush :: ($T: Type, loc := #caller_location) -> *T {
|
|
return ArenaPushSize(size_of(T), loc = loc).(*T);
|
|
}
|
|
|
|
/// ArenaPushSize pushes size bytes to the current context.arena.
|
|
ArenaPushSize :: (size: int, align := DefaultAlign, loc := #caller_location) -> *void {
|
|
if size < 0
|
|
{ Panic("jc: size cannot be negative", loc = loc); }
|
|
TrySetupArena(*context.arena);
|
|
return context.arena.proc(.Acquire, context.arena.data, size, 0, align, null, null, loc);
|
|
}
|
|
|
|
ArenaSave :: (loc := #caller_location) -> ArenaSaveResult {
|
|
TrySetupArena(*context.arena, loc);
|
|
return context.arena.proc(.Save, context.arena.data, 0, 0, 0, null, null, loc).(ArenaSaveResult);
|
|
}
|
|
|
|
ArenaRestore :: (savepoint: ArenaSaveResult, loc := #caller_location) {
|
|
context.arena.proc(.Restore, context.arena.data, 0, 0, 0, null, savepoint.(*void), loc);
|
|
}
|
|
|
|
ArenaAutoRestore :: () #expand {
|
|
wm := ArenaSave();
|
|
`defer ArenaRestore(wm);
|
|
}
|
|
|
|
/// ArenaSaveResult is an arena-specific value used for saving/restoring.
|
|
/// see: ArenaSave
|
|
ArenaSaveResult :: #type,distinct *void;
|
|
|
|
|
|
/// ArenaReset resets arena's state, allowing its memory to be reused.
|
|
ArenaReset :: (loc := #caller_location) {
|
|
TrySetupArena(*context.arena);
|
|
context.arena.proc(.Reset, context.arena.data, 0, 0, 0, null, null, loc);
|
|
}
|
|
|
|
/// ArenaRelease frees the current context.arena's memory.
|
|
ArenaRelease :: (loc := #caller_location) {
|
|
TrySetupArena(*context.arena, loc);
|
|
context.arena.proc(.Teardown, context.arena.data, 0, 0, 0, null, null, loc);
|
|
}
|
|
|
|
/// ArenaToAllocator wraps the given arena, allowing it to be used with Jai's builtin Allocator system.
|
|
ArenaToAllocator :: (arena: *Arena) -> Allocator {
|
|
return .{ proc = JaiAllocatorProc, data = arena };
|
|
}
|
|
|
|
/// PanicArena is an Arena that panics when used.
|
|
PanicArena :: () -> Arena {
|
|
return .{ proc = PanicArenaProc };
|
|
}
|
|
|
|
BumpData :: struct {
|
|
memory: []u8;
|
|
offset: int;
|
|
}
|
|
|
|
BumpArena :: (bump: *BumpData) -> Arena {
|
|
return .{ proc = BumpArenaProc, data = bump };
|
|
}
|
|
|
|
PagingArena :: () -> Arena {
|
|
return .{ proc = PagingArenaProc };
|
|
}
|
|
|
|
|
|
#scope_module
|
|
|
|
TrySetupArena :: (arena: *Arena, loc := #caller_location) #expand {
|
|
if arena.data != null
|
|
{ return; }
|
|
|
|
data := arena.proc(.Setup, null, 0, 0, 0, null, null, loc);
|
|
if data != null
|
|
{ arena.data = data; }
|
|
} @jc.nodocs
|
|
|
|
PanicArenaProc :: (
|
|
event: ArenaEvent,
|
|
arena_data: *void,
|
|
|
|
new_size: int, old_size: int, align: int,
|
|
new_ptr: *void, old_ptr: *void,
|
|
|
|
caller: Source_Code_Location
|
|
) -> *void {
|
|
if event == {
|
|
case .Acquire;
|
|
if new_size > 0
|
|
{ Panic("jc: PanicArena, acquiring memory is not allowed", loc = caller); }
|
|
|
|
case .Save;
|
|
Panic("jc: PanicArena, saving is not allowed", loc = caller);
|
|
case .Restore;
|
|
Panic("jc: PanicArena, restoring is not allowed", loc = caller);
|
|
}
|
|
|
|
return null;
|
|
} @jc.nodocs
|
|
|
|
BumpArenaProc :: (
|
|
event: ArenaEvent,
|
|
arena_data: *void,
|
|
|
|
new_size: int, old_size: int, align: int,
|
|
new_ptr: *void, old_ptr: *void,
|
|
|
|
caller: Source_Code_Location
|
|
) -> *void #no_abc {
|
|
bump := arena_data.(*BumpData);
|
|
if event == {
|
|
case .Setup;
|
|
if bump.memory.data == null
|
|
{ Panic("jc: BumpArena, no backing memory", loc = caller); }
|
|
return arena_data;
|
|
|
|
case .Teardown;
|
|
// Do nothing, we don't own this memory
|
|
|
|
case .Acquire;
|
|
end := bump.memory.data + bump.offset;
|
|
if new_size == 0
|
|
{ return end; }
|
|
|
|
ptr := MemAlignForward(end, align);
|
|
size := new_size + (ptr - end);
|
|
if bump.offset + size > bump.memory.count
|
|
{ Panic("jc: BumpArena, out of memory", loc = caller); }
|
|
|
|
bump.offset += size;
|
|
return ptr;
|
|
case .Reset;
|
|
bump.offset = 0;
|
|
|
|
case .Save;
|
|
return bump.offset.(*void);
|
|
case .Restore;
|
|
wm := old_ptr.(int);
|
|
if wm < 0 || wm >= bump.memory.count
|
|
{ Panic("jc: BumpArena, restored invalid savepoint", loc = caller); }
|
|
bump.offset = wm;
|
|
}
|
|
|
|
return null;
|
|
} @jc.nodocs
|
|
|
|
PagingArenaProc :: (
|
|
event: ArenaEvent,
|
|
arena_data: *void,
|
|
|
|
new_size: int, old_size: int, align: int,
|
|
new_ptr: *void, old_ptr: *void,
|
|
|
|
caller: Source_Code_Location
|
|
) -> *void {
|
|
MaxPages :: 64;
|
|
|
|
PageArenaData :: struct {
|
|
pages: [MaxPages]Page;
|
|
count: int;
|
|
in_use: int;
|
|
|
|
Page :: struct {
|
|
base: *u8;
|
|
offset: int;
|
|
}
|
|
}
|
|
|
|
// @note(judah): page 0 has a special offset because it stores the header
|
|
Page0Offset :: #run AlignForward(size_of(PageArenaData), align_of(PageArenaData));
|
|
|
|
NewPage :: inline (previous_base: *void) -> PageArenaData.Page {
|
|
p: PageArenaData.Page = ---;
|
|
p.base = MemAcquirePage(.CommitImmediately);
|
|
p.offset = 0;
|
|
return p;
|
|
}
|
|
|
|
data := arena_data.(*PageArenaData);
|
|
if event == {
|
|
case .Setup;
|
|
page := NewPage(null);
|
|
data = page.base.(*PageArenaData);
|
|
|
|
page.offset = Page0Offset;
|
|
data.pages[0] = page;
|
|
|
|
data.in_use = 1;
|
|
data.count = 1;
|
|
|
|
return data;
|
|
|
|
case .Teardown;
|
|
// @note(judah): release in reverse oreder so we don't release the memory
|
|
// containing the header until we don't need it anymore.
|
|
i := data.count - 1;
|
|
while i >= 0 {
|
|
page := data.pages[i];
|
|
if page.base != null
|
|
{ MemReleasePage(page.base, PageSize); }
|
|
|
|
i -= 1;
|
|
}
|
|
|
|
case .Acquire;
|
|
Assert(data.count != 0, "jc: PageArena, no pages exist");
|
|
|
|
last := *data.pages[data.in_use - 1];
|
|
end := last.base + last.offset;
|
|
if new_size == 0
|
|
{ return end; }
|
|
|
|
ptr := MemAlignForward(end, align);
|
|
size := new_size + (ptr - end);
|
|
|
|
// create or reuse page
|
|
if last.offset + size > PageSize {
|
|
if data.in_use >= MaxPages
|
|
{ Panic("jc: PageArena, max page count"); }
|
|
|
|
page: *PageArenaData.Page;
|
|
if data.in_use < data.count {
|
|
page = *data.pages[data.in_use];
|
|
}
|
|
else {
|
|
p := NewPage(data.pages[data.count - 1].base);
|
|
data.pages[data.count] = p;
|
|
page = *data.pages[data.count];
|
|
|
|
data.count += 1;
|
|
}
|
|
|
|
data.in_use += 1;
|
|
|
|
ptr = page.base;
|
|
size = new_size;
|
|
last = page;
|
|
}
|
|
|
|
last.offset += size;
|
|
return ptr;
|
|
|
|
case .Reset;
|
|
for 0..data.count - 1 {
|
|
page := *data.pages[it];
|
|
|
|
if it == 0 {
|
|
page.offset = Page0Offset;
|
|
}
|
|
else {
|
|
page.offset = 0;
|
|
}
|
|
}
|
|
|
|
data.in_use = 1;
|
|
|
|
case .Save;
|
|
return data.in_use.(*void);
|
|
|
|
case .Restore;
|
|
in_use_at_save := old_ptr.(int);
|
|
|
|
// @note(judah): We need to restore from the savepoint to count
|
|
// because we could've created more pages inbetween save/restore
|
|
for in_use_at_save..data.count - 1 {
|
|
p := *data.pages[it];
|
|
if it == 0 {
|
|
p.offset = Page0Offset;
|
|
}
|
|
else {
|
|
p.offset = 0;
|
|
}
|
|
}
|
|
|
|
data.in_use = in_use_at_save;
|
|
}
|
|
|
|
return null;
|
|
} @jc.nodocs
|
|
|
|
// @note(judah): TempArenaProc just wraps BumpArenaProc, but provides its own backing memory.
|
|
// This is needed so we can use TempArenaProc without forcing the user to set everything up.
|
|
TempArenaProc :: (
|
|
event: ArenaEvent,
|
|
arena_data: *void,
|
|
|
|
new_size: int, old_size: int, align: int,
|
|
new_ptr: *void, old_ptr: *void,
|
|
|
|
caller: Source_Code_Location
|
|
) -> *void {
|
|
// @temp(judah)
|
|
libc :: #library,system "libc";
|
|
malloc :: (size: int) -> *void #c_call #foreign libc;
|
|
free :: (ptr: *void) #c_call #foreign libc;
|
|
|
|
if event == {
|
|
// @note(judah): allows the temp arena to initialize itself without relying on another arena to provide its memory.
|
|
case .Setup;
|
|
mcount := AlignForward(size_of(BumpData) + 32768).(int);
|
|
memory := malloc(mcount).(*u8);
|
|
MemZero(memory, mcount);
|
|
|
|
bump := memory.(*BumpData);
|
|
bump.memory.data = memory + size_of(BumpData);
|
|
bump.memory.count = mcount - size_of(BumpData);
|
|
|
|
return bump;
|
|
|
|
case .Teardown;
|
|
free(arena_data);
|
|
}
|
|
|
|
return BumpArenaProc(event, arena_data, new_size, old_size, align, new_ptr, old_ptr, caller);
|
|
} @jc.nodocs
|
|
|
|
|
|
#scope_file
|
|
|
|
JaiAllocatorProc :: (event: Allocator_Mode, requested_size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
|
|
CallerFromStackTrace :: () -> Source_Code_Location #expand {
|
|
node := context.stack_trace;
|
|
if node.next != null {
|
|
node = node.next;
|
|
}
|
|
return node.info.location;
|
|
}
|
|
|
|
arena := allocator_data.(*Arena);
|
|
if event == {
|
|
case .STARTUP;
|
|
TrySetupArena(arena);
|
|
return null;
|
|
|
|
case .ALLOCATE;
|
|
loc := CallerFromStackTrace();
|
|
return ArenaPushSize(requested_size, loc = loc,, arena = arena);
|
|
|
|
case .RESIZE;
|
|
loc := CallerFromStackTrace();
|
|
new := ArenaPushSize(requested_size, loc = loc,, arena = arena);
|
|
MemCopy(new, old_memory, old_size);
|
|
return new;
|
|
|
|
case .FREE;
|
|
// Arenas don't free single pointers so give this a value that's obviously incorrect
|
|
MemOverwrite(old_memory, old_size, 0xAA);
|
|
}
|
|
|
|
return null;
|
|
} @jc.nodocs
|
|
|