591 lines
22 KiB
Plaintext
591 lines
22 KiB
Plaintext
// #TODO: Consider how to approach ExpandableArenas
|
|
|
|
#load "arena_platform.jai";
|
|
|
|
// Counted in the arena's own pages, so this is 64 KB on a 4 KB-page arena and 32 MB on one
|
|
// backed by 2 MB large pages. Rounded up to a power of two by arena_init, which is what lets the
|
|
// arena store it as a log2.
|
|
ARENA_DEFAULT_COMMIT_PAGE_COUNT :: 16;
|
|
ARENA_DEFAULT_ALIGNMENT :: 8; // CPU register width
|
|
PLATFORM_MEMORY_PAGE_SIZE :: 4096;
|
|
PLATFORM_MEMORY_PAGE_SIZE_LOG2 :: 12;
|
|
#run assert((1 << PLATFORM_MEMORY_PAGE_SIZE_LOG2) == PLATFORM_MEMORY_PAGE_SIZE);
|
|
PLATFORM_CACHE_LINE_SIZE :: 64; // #Planned: unused today. Reserved for cache-line padding of
|
|
// per-thread or hot arena fields once those exist.
|
|
|
|
Arena_Sizes :: s64.[
|
|
64 * 1024,
|
|
2 * 1024 * 1024,
|
|
64 * 1024 * 1024,
|
|
2 * 1024 * 1024 * 1024,
|
|
64 * 1024 * 1024 * 1024,
|
|
2 * 1024 * 1024 * 1024 * 1024,
|
|
64 * 1024 * 1024 * 1024 * 1024,
|
|
];
|
|
|
|
Arena_Reserve :: enum u8 {
|
|
Size_64K :: 0;
|
|
Size_2M :: 1;
|
|
Size_64M :: 2;
|
|
Size_2G :: 3;
|
|
Size_64G :: 4;
|
|
Size_2T :: 5;
|
|
Size_64T :: 6;
|
|
}
|
|
|
|
#run assert(Arena_Sizes.count == type_info(Arena_Reserve).values.count);
|
|
|
|
Arena_Flags :: enum_flags u8 {
|
|
None :: 0;
|
|
Chained :: 0b00000001; // #Planned: chained/expandable arenas. NO_CHAIN is the only behaviour.
|
|
Is_Bootstrapped :: 0b00000010; // Live: set by arena_set_bootstrap_flag; read by arena_delete
|
|
// and free_pages_down_to.
|
|
Large_Pages :: 0b01000000; // Live: pass it to arena_init or bootstrap_arena. The reservation
|
|
// is backed by the platform's large/huge pages and the arena's
|
|
// page granularity becomes platform_large_page_size() rather
|
|
// than PLATFORM_MEMORY_PAGE_SIZE. Requesting it is a hint: if the
|
|
// OS refuses, the arena falls back to 4 KB pages and records the
|
|
// miss in Arena_Status.Large_Pages_Failed rather than failing to initialize.
|
|
//
|
|
// On Windows and macOS a granted request commits and wires the
|
|
// WHOLE reservation up front -- there is no reserve-now-commit-
|
|
// later form -- so the reserve class is what becomes resident.
|
|
// Those platforms also refuse partial decommit, which makes
|
|
// free_pages_down_to a no-op for such an arena.
|
|
//
|
|
// Large pages are already non-pageable, so a .Large_Pages arena
|
|
// that is also .Secure_Arena skips the per-commit page lock: the
|
|
// guarantee holds without it.
|
|
Secure_Arena :: 0b10000000; // Live: pass it to arena_init or bootstrap_arena. Every page the
|
|
// arena commits is locked into physical memory as it is committed,
|
|
// and unlocked before it is decommitted. Locking a range once at
|
|
// the top level does not survive this arena's growth or its
|
|
// release cycle, which is why it belongs on the commit path.
|
|
}
|
|
|
|
Arena_Status :: enum_flags u8 {
|
|
None :: 0;
|
|
Secure_Lock_Failed :: 0b00000001; // Sticky: a range that spent time unlocked cannot be made retroactively safe by a later success.
|
|
Large_Pages_Failed :: 0b00000010; // Sticky: pages already handed out at 4 KB granularity do not become large ones later.
|
|
}
|
|
|
|
arena_status_is_set :: (arena: *Arena, flag: Arena_Status) -> bool #expand {
|
|
return (arena.status & flag) == flag;
|
|
}
|
|
|
|
arena_is_secure :: (arena: *Arena) -> bool #expand {
|
|
return (arena.flags & .Secure_Arena) == .Secure_Arena;
|
|
}
|
|
|
|
// Whether a secure arena has actually held its guarantee.
|
|
arena_secure_lock_intact :: (arena: *Arena) -> bool {
|
|
if !is_valid(arena) return false;
|
|
return arena_is_secure(arena) && !arena_status_is_set(arena, .Secure_Lock_Failed);
|
|
}
|
|
|
|
arena_is_large_pages :: (arena: *Arena) -> bool #expand {
|
|
return (arena.flags & .Large_Pages) == .Large_Pages;
|
|
}
|
|
|
|
// Whether a large-page arena actually got large pages, as opposed to falling back to 4 KB.
|
|
arena_large_pages_active :: (arena: *Arena) -> bool {
|
|
if !is_valid(arena) return false;
|
|
return arena_is_large_pages(arena) && !arena_status_is_set(arena, .Large_Pages_Failed);
|
|
}
|
|
|
|
arena_page_size :: (arena: *Arena) -> s64 #expand {
|
|
return cast(s64) 1 << arena.page_size_log2;
|
|
}
|
|
|
|
arena_alignment :: (arena: *Arena) -> s64 #expand {
|
|
return cast(s64) 1 << arena.alignment_log2;
|
|
}
|
|
|
|
arena_initial_commit_pages :: (arena: *Arena) -> s64 #expand {
|
|
return cast(s64) 1 << arena.initial_commit_page_count_log2;
|
|
}
|
|
|
|
Arena :: struct {
|
|
current_point:*u8;
|
|
memory_base: *u8;
|
|
first_uncommitted_page: *u8;
|
|
reserve: Arena_Reserve;
|
|
flags: Arena_Flags;
|
|
status: Arena_Status;
|
|
alignment_log2: u8;
|
|
page_size_log2: u8 = PLATFORM_MEMORY_PAGE_SIZE_LOG2; // Set by arena_platform_init. The ground truth for every page calculation.
|
|
initial_commit_page_count_log2: u8;
|
|
#if ARENA_DEBUG {
|
|
high_water_mark: s64;
|
|
label: string;
|
|
file_path: string;
|
|
function_name: string;
|
|
line_number: s32;
|
|
}
|
|
}
|
|
|
|
bootstrap_arena :: (new_reserve: Arena_Reserve, default_commit_page_count: s32 = ARENA_DEFAULT_COMMIT_PAGE_COUNT, flags: Arena_Flags = .None, label:="", loc := #caller_location)
|
|
-> *Arena #expand {
|
|
arena_ptr := bootstrap_arena_internal(new_reserve, default_commit_page_count, flags, label, loc);
|
|
|
|
#if ARENA_DEBUG {
|
|
if arena_ptr {
|
|
push_allocator(context.default_allocator);
|
|
arena_ptr.label = copy_string(label);
|
|
arena_ptr.file_path = copy_string(loc.fully_pathed_filename);
|
|
this_allocation_is_not_a_leak(arena_ptr.label.data);
|
|
this_allocation_is_not_a_leak(arena_ptr.file_path.data);
|
|
arena_ptr.line_number = cast(s32)loc.line_number;
|
|
arena_ptr.function_name = #procedure_name();
|
|
}
|
|
}
|
|
|
|
return arena_ptr;
|
|
}
|
|
|
|
bootstrap_arena_internal :: (new_reserve: Arena_Reserve, default_commit_page_count: s32, flags: Arena_Flags, label: string, loc: Source_Code_Location) -> *Arena {
|
|
new_arena: Arena;
|
|
success, pages_committed := arena_init(*new_arena, new_reserve, default_commit_page_count, flags);
|
|
if !success || !pages_committed {
|
|
arena_delete(*new_arena);
|
|
return null;
|
|
}
|
|
|
|
arena_ptr: *Arena = xx new_arena.memory_base;
|
|
memcpy(arena_ptr, *new_arena, size_of(Arena));
|
|
arena_ptr.current_point = arena_start(arena_ptr);
|
|
|
|
arena_set_bootstrap_flag(arena_ptr);
|
|
|
|
return arena_ptr;
|
|
}
|
|
|
|
arena_allocator_proc :: (mode: Allocator_Mode, requested_size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
|
|
arena := cast(*Arena)allocator_data;
|
|
|
|
if #complete mode == {
|
|
case .FREE;
|
|
return null;
|
|
case .RESIZE; #through;
|
|
case .ALLOCATE;
|
|
result := arena_alloc(arena, requested_size);
|
|
if mode == .RESIZE {
|
|
size_to_copy := ifx old_size < requested_size then old_size else requested_size;
|
|
if result && size_to_copy memcpy(result, old_memory, size_to_copy);
|
|
}
|
|
return result;
|
|
|
|
case .STARTUP; #through;
|
|
case .SHUTDOWN;
|
|
return null;
|
|
|
|
case .THREAD_START; #through;
|
|
case .THREAD_STOP;
|
|
return null;
|
|
|
|
case .CREATE_HEAP; #through;
|
|
case .DESTROY_HEAP;
|
|
return null;
|
|
|
|
case .IS_THIS_YOURS;
|
|
if !arena return null;
|
|
// The contract is 'allocated by your heap and is currently live'.
|
|
if old_memory >= arena_start(arena) && old_memory < arena.current_point {
|
|
return cast(*void)true;
|
|
}
|
|
return null;
|
|
|
|
case .CAPS;
|
|
if old_memory { (cast(*string)old_memory).* = "modules/Basic arena_allocator_proc"; }
|
|
return cast(*void)(Allocator_Caps.IS_THIS_YOURS | .HINT_I_AM_A_FAST_BUMP_ALLOCATOR);
|
|
}
|
|
}
|
|
|
|
arena_init :: (using arena: *Arena, new_reserve: Arena_Reserve, commit_page_count:=ARENA_DEFAULT_COMMIT_PAGE_COUNT, new_flags: Arena_Flags = .None)
|
|
-> (success: bool, pages_committed: s64 = 0) {
|
|
if arena == null {
|
|
log_error("[Basic/Arena] Attempting to initialize a null arena!");
|
|
return false;
|
|
}
|
|
|
|
if (arena.memory_base == null) {
|
|
// Before arena_platform_init, so .Large_Pages reaches the reserve call, and before the first
|
|
// commit, so .Secure_Arena locks through the same path every later growth uses.
|
|
flags |= new_flags;
|
|
|
|
if !arena_platform_init(arena, new_reserve) {
|
|
return false;
|
|
}
|
|
current_point = arena_start(arena);
|
|
arena_set_alignment(arena, ARENA_DEFAULT_ALIGNMENT);
|
|
|
|
commit_pages := ifx commit_page_count > 0
|
|
then cast(s64) 1 << ceil_log2(commit_page_count)
|
|
else cast(s64) commit_page_count;
|
|
|
|
// Page size is not known until the reserve has happened, so the commit size is sized here.
|
|
commit_size := commit_pages * arena_page_size(arena);
|
|
if commit_size < 0 {
|
|
log_warning("[Basic/Arena] Invalid commit_page_count passed to `arena_init`. Using default parameter: % pages",
|
|
ARENA_DEFAULT_COMMIT_PAGE_COUNT);
|
|
commit_size = ARENA_DEFAULT_COMMIT_PAGE_COUNT * arena_page_size(arena);
|
|
}
|
|
|
|
bytes_committed := arena_commit_first_pages(arena, commit_size);
|
|
|
|
committed_pages := bytes_committed >> page_size_log2;
|
|
if committed_pages <= 0 return true, 0; // The commit failed; bootstrap_arena_internal checks for this.
|
|
|
|
initial_commit_page_count_log2 = log2_of_power_of_two(committed_pages);
|
|
|
|
return true, committed_pages;
|
|
}
|
|
|
|
return true, arena_initial_commit_pages(arena); // already initialized
|
|
}
|
|
|
|
// start_offset_bytes is for when we're doing arena-backed structures such as Arena_Array and we may need more
|
|
// space than `commit_size` because we're using some space in the first page.
|
|
// #Planned: no caller passes it yet -- Arena_Array does not exist. It is implemented and guarded,
|
|
// so it works if you pass it, but it is untested and should be treated as such until it has a user.
|
|
arena_commit_first_pages :: (using arena: *Arena, commit_size: s64, start_offset_bytes: s64 = 0)
|
|
-> (bytes_committed: s64) {
|
|
assert(start_offset_bytes >= 0);
|
|
if (start_offset_bytes < 0) {
|
|
log_error("[Basic/Arena] Invalid start_offset_bytes parameter provided to arena_commit_first_pages! Given: %", start_offset_bytes);
|
|
return 0;
|
|
}
|
|
|
|
commit_size_final := commit_size;
|
|
|
|
// Clamp commit size:
|
|
if commit_size <= 0 {
|
|
commit_size_final = ARENA_DEFAULT_COMMIT_PAGE_COUNT * arena_page_size(arena);
|
|
log_warning("[Basic/Arena] Requested commit size is invalid! Got % expected 0 < commit_size <= %. Using default of %!",
|
|
commit_size, reserve_size_bytes(arena), commit_size_final);
|
|
}
|
|
if commit_size > reserve_size_bytes(arena) {
|
|
log_warning("[Basic/Arena] Requested commit size is out of expected range! Got % expected 0 < commit_size <= %. Clamping to max size!",
|
|
commit_size, reserve_size_bytes(arena));
|
|
commit_size_final = reserve_size_bytes(arena);
|
|
}
|
|
|
|
final_address := Align(memory_base + commit_size_final + start_offset_bytes, arena_page_size(arena));
|
|
|
|
commit_size_final = cast(s64)(final_address - memory_base);
|
|
|
|
if (commit_size_final > reserve_size_bytes(arena)) {
|
|
log_warning("[Basic/Arena] Requested commit size larger than available address space! Got % expected <= %. Clamping to max size!",
|
|
commit_size_final, reserve_size_bytes(arena));
|
|
commit_size_final = reserve_size_bytes(arena);
|
|
}
|
|
|
|
final_address = memory_base + commit_size_final;
|
|
|
|
if !extend_committed_pages(arena, final_address) {
|
|
return 0;
|
|
}
|
|
|
|
return arena_committed_bytes(arena);
|
|
}
|
|
|
|
arena_reset_keeping_memory :: arena_reset;
|
|
arena_reset :: (using arena: *Arena) {
|
|
if !is_valid(arena) return;
|
|
current_point = arena_start(arena);
|
|
}
|
|
|
|
arena_release :: (using arena: *Arena) {
|
|
if !is_valid(arena) return;
|
|
current_point = arena_start(arena);
|
|
|
|
free_pages_down_to(arena, arena_initial_commit_pages(arena));
|
|
}
|
|
|
|
arena_reset_overwriting_memory :: (using arena: *Arena, $wipe_function:=memory_secure_overwrite) {
|
|
if !is_valid(arena) return;
|
|
arena_wipe(arena_start(arena), current_point, wipe_function);
|
|
arena_reset(arena);
|
|
}
|
|
|
|
arena_alloc :: (using arena: *Arena, byte_count: s64) -> *void {
|
|
if !arena {
|
|
log_error("[Basic/Arena] Passed a null pointer to arena_alloc!");
|
|
return null;
|
|
}
|
|
|
|
if arena.memory_base == null {
|
|
log_error("Attempting to allocate using an uninitialized Arena!\n Initializing with a 64GB capacity.");
|
|
|
|
arena.reserve = .Size_64G;
|
|
success := arena_init(arena, arena.reserve);
|
|
|
|
if !success return null;
|
|
}
|
|
|
|
if byte_count < 0 {
|
|
log_error("[Basic/Arena] Arena %: negative allocation size %.\n", arena, byte_count);
|
|
return null;
|
|
}
|
|
|
|
result := Align(current_point, arena_alignment(arena));
|
|
|
|
if byte_count > arena_address_limit(arena) - result {
|
|
log_error("[Basic/Arena] Attempt to allocate, but Arena % is full! (Allocation size %; alignment %; address space size %; already allocated %.)\n",
|
|
arena, byte_count, arena_alignment(arena), reserve_size_bytes(arena), current_point - memory_base);
|
|
return null;
|
|
}
|
|
|
|
result_end := byte_count + result;
|
|
if result_end > first_uncommitted_page {
|
|
if !extend_committed_pages(arena, result_end) return null;
|
|
}
|
|
|
|
current_point = result_end;
|
|
|
|
update_high_water_mark(arena);
|
|
|
|
return result;
|
|
}
|
|
|
|
arena_secure_overwrite_starting_from :: (arena: *Arena, start: *u8) {
|
|
arena_wipe(start, arena.first_uncommitted_page);
|
|
}
|
|
|
|
Arena_Secure_Overwrite_Endpoint :: enum s32 {
|
|
Current_Point; // erase from arena_start to the current point
|
|
First_Uncommitted_Page;
|
|
}
|
|
|
|
arena_secure_overwrite :: (arena: *Arena, $endpoint: Arena_Secure_Overwrite_Endpoint=.Current_Point) {
|
|
#if endpoint == {
|
|
case .Current_Point;
|
|
arena_wipe(arena_start(arena), arena.current_point);
|
|
case .First_Uncommitted_Page;
|
|
arena_wipe(arena_start(arena), arena.first_uncommitted_page);
|
|
}
|
|
}
|
|
|
|
// Arena @Utility
|
|
arena_set_alignment :: inline (arena: *Arena, alignment: s64) {
|
|
assert(is_valid(arena));
|
|
assert(alignment > 0 && (alignment & (alignment - 1)) == 0,
|
|
"Arena alignment must be a non-zero power of two; got %.", alignment);
|
|
if alignment <= 0 || (alignment & (alignment - 1)) != 0 {
|
|
log_error("[Basic/Arena][arena_set_alignment] Attempting to set an invalid alignment `%`! Keeping original value `%`",
|
|
alignment, arena_alignment(arena));
|
|
return;
|
|
}
|
|
|
|
limit := reserve_size_bytes(arena);
|
|
if alignment > limit {
|
|
log_error("[Basic/Arena][arena_set_alignment] Attempting to set an alignment `%` wider than the % byte reservation! Clamping to the reservation",
|
|
alignment, limit);
|
|
arena.alignment_log2 = log2_of_power_of_two(limit);
|
|
return;
|
|
}
|
|
|
|
arena.alignment_log2 = log2_of_power_of_two(alignment);
|
|
}
|
|
|
|
arena_set_bootstrap_flag :: (arena: *Arena) #expand {
|
|
arena.flags = (arena.flags | .Is_Bootstrapped);
|
|
}
|
|
|
|
arena_is_bootstrapped :: (arena: *Arena) -> bool #expand {
|
|
return (arena.flags & .Is_Bootstrapped == .Is_Bootstrapped);
|
|
}
|
|
|
|
update_high_water_mark :: (arena: *Arena) #expand {
|
|
#if ARENA_DEBUG {
|
|
current_usage := arena_usage_bytes(arena);
|
|
if (current_usage > arena.high_water_mark) {
|
|
arena.high_water_mark = current_usage;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every arena reserves `size_of(Arena)` bytes at its base, whether or not it is bootstrapped.
|
|
//
|
|
// The reason is not the cost of testing `arena_is_bootstrapped` here. This is a macro, and
|
|
// `flags` shares a cache line with `memory_base`, so the test would be a byte load, an AND and a
|
|
// CMOV, predicted perfectly because the bit never changes after init, less than `arena_alloc`
|
|
// already spends around this call. What a branch would cost instead:
|
|
//
|
|
// - `arena_start` would stop being a pure function of `memory_base`, which is written once at
|
|
// init. Every caller that compares two of its results across an arena's life: `arena_usage_bytes`,
|
|
// the IS_THIS_YOURS range test, the reset paths, the `Arena_State` marks
|
|
// depends on it not moving, and `flags` is mutable and public.
|
|
//
|
|
// - The flag is not set yet at the two places that need it most. `arena_init` runs on a stack
|
|
// local before anything knows the arena will be bootstrapped, and `bootstrap_arena_internal`
|
|
// computes `current_point` from here one line before `arena_set_bootstrap_flag`, so a
|
|
// branching version would park `current_point` on top of the header it means to skip.
|
|
//
|
|
// Note that `size_of(Arena)` is larger under ARENA_DEBUG, so `arena_start` does not return the
|
|
// same offset in debug and release builds. *Never persist an offset into arena memory across a
|
|
// change of build configuration*
|
|
arena_start :: (arena: *Arena) -> *u8 #expand {
|
|
return Align(arena.memory_base + size_of(Arena), ARENA_DEFAULT_ALIGNMENT);
|
|
}
|
|
|
|
arena_address_limit :: (arena: *Arena) -> *u8 #expand {
|
|
return (arena.memory_base + reserve_size_bytes(arena));
|
|
}
|
|
|
|
arena_usage_bytes :: (arena: *Arena) -> s64 #expand {
|
|
return cast(s64)(arena.current_point - arena_start(arena));
|
|
}
|
|
|
|
arena_committed_bytes :: (arena: *Arena) -> s64 #expand {
|
|
return cast(s64)(arena.first_uncommitted_page - arena.memory_base);
|
|
}
|
|
|
|
reserve_size_bytes :: (arena_reserve: Arena_Reserve) -> s64 {
|
|
return Arena_Sizes[cast(s32)arena_reserve];
|
|
}
|
|
|
|
reserve_size_bytes :: (arena: *Arena) -> s64 #expand {
|
|
return reserve_size_bytes(arena.reserve);
|
|
}
|
|
|
|
is_valid :: (arena: *Arena) -> bool {
|
|
return (arena != null && arena.memory_base != null);
|
|
}
|
|
|
|
get_allocator :: (arena: *Arena) -> Allocator #expand {
|
|
return .{arena_allocator_proc, arena};
|
|
}
|
|
|
|
next_reserve :: (size: s64, alignment: s64) -> Arena_Reserve {
|
|
size_with_overhead := Align(size + size_of(Arena), alignment);
|
|
|
|
for i: 0..enum_count(Arena_Reserve)-1 {
|
|
if size_with_overhead <= Arena_Sizes[i] {
|
|
return cast(Arena_Reserve)i;
|
|
}
|
|
}
|
|
|
|
return .Size_64T;
|
|
}
|
|
|
|
// @Utility `push_allocator` style macros
|
|
push_arena :: (arena: *Arena) #expand #no_debug {
|
|
assert(is_valid(arena));
|
|
|
|
old_allocator := context.allocator;
|
|
context.allocator = .{arena_allocator_proc, arena};
|
|
|
|
`defer context.allocator = old_allocator;
|
|
}
|
|
|
|
push_alignment :: (arena: *Arena, alignment: s64) #expand #no_debug {
|
|
assert(is_valid(arena));
|
|
assert(alignment > 0 && (alignment & (alignment - 1)) == 0,
|
|
"Arena alignment must be a non-zero power of two; got %.", alignment);
|
|
old_alignment := arena_alignment(arena);
|
|
if alignment <= 0 || (alignment & (alignment - 1)) != 0 {
|
|
log_error("[Basic/Arena][push_alignment] Attempting to set an invalid alignment `%`! Keeping original value `%`",
|
|
alignment, old_alignment);
|
|
return;
|
|
}
|
|
arena_set_alignment(arena, alignment);
|
|
|
|
`defer arena_set_alignment(arena, old_alignment);
|
|
}
|
|
|
|
auto_reset_overwriting_memory :: (arena: *Arena, $wipe_function:=memory_secure_overwrite) #expand #no_debug {
|
|
__mark := get_arena_state(arena);
|
|
`defer restore_arena_state(arena, __mark, false, wipe_function);
|
|
}
|
|
|
|
auto_release_overwriting_memory :: (arena: *Arena, $wipe_function:=memory_secure_overwrite) #expand #no_debug {
|
|
__mark := get_arena_state(arena);
|
|
`defer restore_arena_state(arena, __mark, true, wipe_function);
|
|
}
|
|
|
|
auto_reset :: (arena: *Arena) #expand {
|
|
__mark := get_arena_state(arena);
|
|
`defer restore_arena_state(arena, __mark, false, wipe_function=null);
|
|
}
|
|
|
|
auto_release :: (arena: *Arena) #expand {
|
|
__mark := get_arena_state(arena);
|
|
`defer restore_arena_state(arena, __mark, true, wipe_function=null);
|
|
}
|
|
|
|
// Temporary for debugging:
|
|
arena_details :: (arena: *Arena, loc := #caller_location) -> string {
|
|
push_allocator(temp);
|
|
|
|
builder: String_Builder;
|
|
|
|
if arena == null {
|
|
print_to_builder(*builder, "(arena_details) Passed a null pointer @ %\n", loc);
|
|
return builder_to_string(*builder);
|
|
}
|
|
|
|
print_to_builder(*builder, "Details for Arena % with memory_base %\n", arena, arena.memory_base);
|
|
|
|
if arena.memory_base == null {
|
|
return builder_to_string(*builder);
|
|
}
|
|
|
|
reserve := arena_address_limit(arena) - arena.memory_base;
|
|
committed_size := arena.first_uncommitted_page - arena.memory_base;
|
|
used_size := arena.current_point - arena_start(arena);
|
|
|
|
print_to_builder(*builder, " - committed: %, used: %, reserve: %\n", format_bytes(committed_size), format_bytes(used_size), format_bytes(reserve));
|
|
print_to_builder(*builder, " - page size: %\n", format_bytes(arena_page_size(arena)));
|
|
if arena_is_large_pages(arena) {
|
|
print_to_builder(*builder, " - large pages: % (requested)\n",
|
|
ifx arena_status_is_set(arena, .Large_Pages_Failed) then "unavailable, fell back" else "active");
|
|
}
|
|
|
|
return builder_to_string(*builder);
|
|
}
|
|
|
|
arena_wipe :: (start: *u8, end: *u8, $wipe_function:=memory_secure_overwrite) #expand {
|
|
#if wipe_function {
|
|
if end <= start return;
|
|
wipe_function(start, cast(s64)(end - start));
|
|
}
|
|
}
|
|
#scope_module
|
|
Arena_State :: struct {
|
|
starting_arena : *Arena;
|
|
starting_point : *u8;
|
|
}
|
|
|
|
get_arena_state :: inline (arena: *Arena) -> Arena_State {
|
|
assert(arena != null);
|
|
|
|
state: Arena_State = ---;
|
|
state.starting_arena = arena;
|
|
state.starting_point = state.starting_arena.current_point;
|
|
|
|
return state;
|
|
}
|
|
|
|
restore_arena_state :: inline (arena: *Arena, state: Arena_State, $free_pages: bool, $wipe_function:=memory_secure_overwrite) {
|
|
assert(arena == state.starting_arena);
|
|
assert(is_valid(state.starting_arena));
|
|
|
|
#if wipe_function {
|
|
arena_wipe(state.starting_point, state.starting_arena.current_point, wipe_function);
|
|
}
|
|
|
|
state.starting_arena.current_point = state.starting_point;
|
|
|
|
#if free_pages {
|
|
using state.starting_arena;
|
|
|
|
last_page := cast(*void)Align(current_point, arena_page_size(arena));
|
|
keep_page_count := cast(s64)(last_page - memory_base) >> page_size_log2;
|
|
initial_pages := arena_initial_commit_pages(arena);
|
|
if keep_page_count < initial_pages keep_page_count = initial_pages;
|
|
|
|
free_pages_down_to(arena, keep_page_count);
|
|
}
|
|
}
|