improve memory module

This commit is contained in:
Judah Caruso 2025-05-24 12:59:52 -06:00
parent 30cdb1b441
commit 657dec6415
3 changed files with 219 additions and 91 deletions

135
memory/allocators.jai Normal file
View file

@ -0,0 +1,135 @@
make_crash_allocator :: () -> Allocator {
return .{ proc = crash_allocator_proc };
}
crash_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
message: string;
if mode == {
case .ALLOCATE;
message = basic.tprint("Attempt to allocate % byte(s) using the crash allocator!", size);
case .RESIZE;
message = basic.tprint("Attempt to resize (from % to % byte(s)) using the crash allocator!", old_size, size);
case .FREE;
message = basic.tprint("Attempt to free % byte(s) using the crash allocator!", size);
}
loc := meta.get_stack_trace_caller_location();
basic.assert(false, message, loc = loc);
debug_break();
return null;
}
Arena :: struct {
memory: *void;
memory_size: u64;
offset: u64;
}
init_arena :: (a: *Arena, memory: *void, size: u64) {
a.memory = memory;
a.memory_size = size;
a.offset = 0;
}
make_arena_allocator :: (arena: *Arena) -> Allocator {
return .{
data = arena,
proc = xx arena_allocator_proc,
};
}
Extended_Allocator_Mode :: enum {
using Allocator_Mode;
request_memory :: Allocator_Mode.ALLOCATE;
resize_memory :: Allocator_Mode.RESIZE;
release_memory :: Allocator_Mode.FREE;
first_time_used :: Allocator_Mode.STARTUP;
release_everything :: Allocator_Mode.SHUTDOWN;
reset_state;
save_point; // should return *s64
restore_save_point; // 'old_size' will be the dereferenced return value of 'save_point'
}
arena_allocator_proc :: (mode: Extended_Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
arena := allocator_data.(*Arena);
if mode == {
case .request_memory;
return arena_alloc(arena, size);
case .resize_memory;
if old_memory == null {
return arena_alloc(arena, size);
}
if size == 0 {
return null;
}
if size == old_size {
return old_memory;
}
new_memory := arena_alloc(arena, size);
memcpy(new_memory, old_memory, old_size);
return new_memory;
case .save_point;
return *arena.offset;
case .restore_save_point;
arena.offset = old_size.(u64);
}
return null;
}
arena_alloc :: (a: *Arena, count: int, alignment := Default_Align, loc := #caller_location) -> *void {
basic.assert(a.memory != null, "arena: not initialized", loc = loc);
basic.assert(power_of_two(alignment));
end := a.memory.(*u8) + a.offset;
ptr := align_forward(end.(int), alignment);
total_size := (count + ptr.(*u8) - end.(*u8)).(u64);
basic.assert(a.offset + total_size <= a.memory_size, "arena: out of memory", loc = loc);
a.offset += total_size;
return ptr.(*void);
}
#scope_file;
basic :: #import "Basic"; // @future
meta :: #import "jc/meta";
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RUN_TESTS {
test :: #import "jc/test";
#run {
test.run("arena:basic", t => {
memory := request_memory(1 * Kilobyte);
defer release_memory(memory);
arena: Arena;
init_arena(*arena, memory, 1 * Kilobyte);
context.allocator = make_arena_allocator(*arena);
save_point := allocator_save();
i := request_memory(int);
basic.assert(i != null);
basic.assert(arena.offset == size_of(int));
allocator_restore(save_point);
});
}
}

View file

@ -6,10 +6,10 @@ Gigabyte :: 1024 * Megabyte;
Default_Align :: #run 2 * align_of(*void); Default_Align :: #run 2 * align_of(*void);
align_of :: ($T: Type) -> u64 #expand { align_of :: ($T: Type) -> int #expand {
return #run -> u64 { return #run -> int {
info := type_info(struct{ p: u8; t: T; }); info := type_info(struct{ p: u8; t: T; });
return info.members[1].offset_in_bytes.(u64); return info.members[1].offset_in_bytes.(int);
}; };
} }
@ -29,12 +29,12 @@ bitcast :: ($T: Type, expr: Code) -> T #expand {
return (*value).(*T).*; return (*value).(*T).*;
} }
power_of_two :: (x: u64) -> bool { power_of_two :: (x: int) -> bool {
if x == 0 return false; if x == 0 return false;
return x & (x - 1) == 0; return x & (x - 1) == 0;
} }
align_forward :: (ptr: u64, align: u64 = Default_Align) -> u64 { align_forward :: (ptr: int, align: int = Default_Align) -> int {
basic.assert(power_of_two(align), "alignment must be a power of two"); basic.assert(power_of_two(align), "alignment must be a power of two");
p := ptr; p := ptr;
@ -57,7 +57,28 @@ init_or_zero :: inline (ptr: *$T, custom_init: (*T) = null) {
} }
} }
make :: ($T: Type, reserved := 0, $init := true) -> [..]T allocator_reset :: () {
allocator := context.allocator;
allocator.proc(xx Extended_Allocator_Mode.reset_state, 0, 0, null, allocator.data);
}
allocator_save :: () -> int {
allocator := context.allocator;
return allocator.proc(xx Extended_Allocator_Mode.save_point, 0, 0, null, allocator.data).(*int).*;
}
allocator_restore :: (save_point: int) {
allocator := context.allocator;
allocator.proc(xx Extended_Allocator_Mode.restore_save_point, 0, save_point, null, allocator.data);
}
request_memory :: (size: int, align := Default_Align) -> *void {
allocator := context.allocator;
aligned_size := align_forward(size, align);
return allocator.proc(xx Extended_Allocator_Mode.request_memory, aligned_size.(int), 0, null, allocator.data);
}
request_memory :: ($T: Type, reserved := 0, $init := true) -> [..]T
#modify { #modify {
ok, info := meta.type_is_array(T); ok, info := meta.type_is_array(T);
if ok && info.array_type == .RESIZABLE { if ok && info.array_type == .RESIZABLE {
@ -68,9 +89,8 @@ make :: ($T: Type, reserved := 0, $init := true) -> [..]T
return false; return false;
} }
{ {
size := align_forward(size_of(T) * basic.max(reserved, 0).(u64)).(s64); size := size_of(T) * basic.max(reserved, 0);
data := basic.alloc(size); data := request_memory(size, align_of(T)).(*T);
#if init if size != 0 { #if init if size != 0 {
memset(data, 0, size); memset(data, 0, size);
} }
@ -83,86 +103,32 @@ make :: ($T: Type, reserved := 0, $init := true) -> [..]T
return arr; return arr;
} }
make :: ($T: Type, $init := true) -> *T request_memory :: ($T: Type, $init := true) -> *T
#modify { return !meta.type_is_array(T); } #modify { return !meta.type_is_array(T); }
{ {
ptr := basic.alloc(size_of(T)).(*T); ptr := request_memory(size_of(T), align_of(T)).(*T);
#if init init_or_zero(ptr); #if init init_or_zero(ptr);
return ptr; return ptr;
} }
Crash_Allocator :: Allocator.{ proc = crash_allocator_proc }; release_memory :: inline (ptr: *void) {
allocator := context.allocator;
crash_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void { allocator.proc(xx Extended_Allocator_Mode.release_memory, 0, 0, ptr, allocator.data);
message: string;
if mode == {
case .ALLOCATE;
message = basic.tprint("Attempt to allocate % byte(s) using the crash allocator!", size);
case .RESIZE;
message = basic.tprint("Attempt to resize (from % to % byte(s)) using the crash allocator!", old_size, size);
case .FREE;
message = basic.tprint("Attempt to free % byte(s) using the crash allocator!", size);
}
basic.assert(false, message);
debug_break();
return null;
} }
Arena :: struct { release_memory :: inline (arr: []$T) {
memory: *void; release_memory(arr.data);
memory_size: u64;
offset: u64;
} }
init_arena :: (a: *Arena, memory: *void, size: u64) { release_memory :: inline (arr: [..]$T) {
a.memory = memory; release_memory(arr.data,, allocator = arr.allocator);
a.memory_size = size;
a.offset = 0;
} }
arena_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void { release_memory :: inline (str: string) {
arena := allocator_data.(*Arena); release_memory(str.data);
if mode == {
case .ALLOCATE;
return arena_alloc(arena, size);
case .RESIZE;
if old_memory == null {
return arena_alloc(arena, size);
}
if size == 0 {
return null;
}
if size == old_size {
return old_memory;
}
new_memory := arena_alloc(arena, size);
memcpy(new_memory, old_memory, old_size);
return new_memory;
case;
}
return null;
}
arena_alloc :: (a: *Arena, count: int, alignment := Default_Align, loc := #caller_location) -> *void {
basic.assert(a.memory != null, "arena: not initialized", loc = loc);
basic.assert(power_of_two(alignment));
end := a.memory.(*u8) + a.offset;
ptr := align_forward(end.(u64), alignment);
total_size := (count + ptr.(*u8) - end.(*u8)).(u64);
basic.assert(a.offset + total_size <= a.memory_size, "arena: out of memory", loc = loc);
a.offset += total_size;
return ptr.(*void);
} }
#load "allocators.jai";
#scope_file; #scope_file;
@ -176,25 +142,33 @@ compiler :: #import "Compiler"; // @future
// TESTS // TESTS
// ---------------------------------------------------------- // ----------------------------------------------------------
#if RUN_TESTS #run { #if RUN_TESTS {
test :: #import "jc/test"; test :: #import "jc/test";
test.run("make:dynamic arrays", (t) => { #run {
a1 := make([..]int); test.run("request_memory:dynamic arrays", (t) => {
test.expect(t, a1.count == 0); a1 := request_memory([..]int);
test.expect(t, a1.allocated == 0); defer release_memory(a1);
basic.array_add(*a1, 10, 20, 30); test.expect(t, a1.count == 0);
test.expect(t, a1.count == 3); test.expect(t, a1.allocated == 0);
test.expect(t, a1.allocated != 0, "%", a1.allocated);
a2 := make([..]int, 8); basic.array_add(*a1, 10, 20, 30);
test.expect(t, a2.count == 0); test.expect(t, a1.count == 3);
test.expect(t, a2.allocated == 8); test.expect(t, a1.allocated != 0, "%", a1.allocated);
});
test.run("make:values", (t) => { a2 := request_memory([..]int, 8);
v1 := make(int); defer release_memory(a2);
test.expect(t, v1.* == 0);
}); test.expect(t, a2.count == 0);
test.expect(t, a2.allocated == 8);
});
test.run("request_memory:values", (t) => {
v1 := request_memory(int);
defer release_memory(v1);
test.expect(t, v1.* == 0);
});
}
} }

View file

@ -1,5 +1,24 @@
#module_parameters(RUN_TESTS := false); #module_parameters(RUN_TESTS := false);
get_stack_trace_caller_location :: (loc := #caller_location) -> Source_Code_Location {
if context.stack_trace == null || context.stack_trace.info == null {
return loc;
}
cur := context.stack_trace;
while cur != null {
if cur.info == null break;
if cur.info.location.fully_pathed_filename != loc.fully_pathed_filename {
break;
}
cur = cur.next;
}
return cur.info.location;
}
// Can be passed directly to using,map // Can be passed directly to using,map
remap_snake_to_pascal :: (names: []string) { remap_snake_to_pascal :: (names: []string) {
for names { for names {