// Simple procedures that many programs may want to use. #load "Array.jai"; #load "Simple_String.jai"; #load "String_Builder.jai"; #load "Print.jai"; #load "Int128.jai"; #load "Apollo_Time.jai"; #load "string_to_float.jai"; #load "float_to_string.jai"; #load "fixed_arena.jai"; #load "serialization.jai"; #load "arena.jai"; #load "enums.jai"; #load "timing.jai"; #if (OS == .WINDOWS) || (OS == .PS5) || (OS == .NN_SWITCH2) || (OS == .LINUX) || (OS == .MACOS) { #load "Temporary_Storage_Paged_Alloc.jai"; // @Incomplete: Flat_Pool version. } else { #load "Temporary_Storage_Paged_Alloc.jai"; } // The suites for arena.jai and fixed_arena.jai live with the code they test so they can reach // its module scope. They export run_arena_tests / run_fixed_arena_tests and nothing else; // everything they use internally is #scope_file. #if LOAD_TESTS { #load "tests/arena_tests.jai"; #load "tests/fixed_arena_tests.jai"; } #module_parameters () (MEMORY_DEBUGGER := false, ENABLE_ASSERT := true, REPLACEMENT_INTERFACE: $I/interface Memory_Debugger_Interface = Memory_Debugger_Interface, VISUALIZE_MEMORY_DEBUGGER := true, TEMP_ALLOCATOR_OVERWRITE_MEMORY := false, ARENA_DEBUG := false, LOAD_TESTS := false, ENABLE_TIMING := true) { // Your custom-supplied memory debugger should conform to this interface: Memory_Debugger_Interface :: struct { check_alloc :: inline (allocator: Allocator, memory: *void, size: s64) {}; check_free :: inline (allocator: Allocator, memory: *void) {}; check_create_heap :: inline (a: Allocator) {}; check_destroy_heap :: inline (a: Allocator) {}; } }; // secure_overwrite / memory_secure_overwrite used to live here. arena.jai still takes // memory_secure_overwrite as its default $wipe_function. Only the main program may supply // module parameters, so this import gets Secure_Memory at its defaults -- which is why its // USE_SODIUM defaults to true rather than being something callers switch on. #import "Secure_Memory"; // VISUALIZE_MEMORY_DEBUGGER and REPLACEMENT_INTERFACE only do anything if MEMORY_DEBUGGER == true. // ENABLE_TIMING compiles in the tick-based timers in timing.jai. // TEMP_ALLOCATOR_OVERWRITE_MEMORY memset free memory values to help track down use-after-free issues SET_STORAGE_MARK_STAMP :: 0xda; RESET_STORAGE_STAMP :: 0xdb; FREE_PAGE_STAMP :: 0xdc; // // modules/Basic is intended to include a bunch of stuff that most programs will want to do; // however, it is also supposed to be completely replaceable, so that you don't have to // run on top of it if you don't want to. // // We could definitely use some comprehensive documentation on this module! // #if MEMORY_DEBUGGER { #load "Memory_Debugger.jai"; #if VISUALIZE_MEMORY_DEBUGGER #load "Visualize_Memory_Debugger.jai"; #if REPLACEMENT_INTERFACE == Memory_Debugger_Interface { // Memory_Debugger_Interface is a sentinel; it means the user didn't pass a replacement, so if we see this, we just use our own stuff. Memory_Debugger :: struct { check_alloc :: _check_alloc; check_free :: _check_free; check_create_heap :: _check_create_heap; check_destroy_heap :: _check_destroy_heap; } } else { Memory_Debugger :: REPLACEMENT_INTERFACE; } } else { // Declare this routine as a dummy so it can be used throughout the user-level code without #iffing it. this_allocation_is_not_a_leak :: (memory: *void) #expand #no_debug {} this_allocation_leaks_todo :: (memory: *void) #expand #no_debug {} this_allocation_does_not_propagate_leak_ownership :: (memory: *void) #expand #no_debug {} report_memory_leaks :: () {}; } #if !(MEMORY_DEBUGGER && VISUALIZE_MEMORY_DEBUGGER) { memory_visualizer_per_frame_update :: () #expand #no_debug {} } get_command_line_arguments :: () -> [] string { c_args := __command_line_arguments; result := NewArray(c_args.count, string); for c_args result[it_index] = to_string(it); return result; } assert_helper :: (message := "", args: .. Any, loc := #caller_location) -> bool { if context.handling_assertion_failure return false; // Avoid infinite loops. context.handling_assertion_failure = true; // We provide 'loc' in case _STACK_TRACE is turned off, but assertion_failed may well // look at the stack trace if it is available. should_break := context.assertion_failed(loc, tprint(message, ..args)); context.handling_assertion_failure = false; return should_break; } #if ENABLE_ASSERT { // We make assert() a macro so that it doesn't annoy you by being on the call stack. // To keep it short we factor most of the code into assert_helper() (which you could call // from your own assert implementation, as it is included in the module even if ENABLE_ASSERT // is false). assert :: (arg: bool, message := "", args: .. Any, loc := #caller_location) #no_debug #expand { if !arg && assert_helper(message, ..args, loc) then debug_break(); } } else { assert :: (#discard arg: bool, #discard message := "", #discard args: .. Any, #discard loc := #caller_location) #expand {} // assert :: (arg: bool, message := "", args: .. Any, loc := #caller_location) #expand {} } alloc :: (size: s64) -> *void { a := context.allocator; result := a.proc(.ALLOCATE, size, 0, null, a.data); #if MEMORY_DEBUGGER Memory_Debugger.check_alloc(a, result, size); return result; } free :: (memory: *void) { a := context.allocator; #if MEMORY_DEBUGGER Memory_Debugger.check_free(a, memory); a.proc(.FREE, 0, 0, memory, a.data); } realloc :: (memory: *void, size: s64, old_size: s64) -> *void { a := context.allocator; // We must not just do a "check_realloc" at the end (as previous versions of this function did) // because that opens us up to race conditions where the memory debugger sees events in a different order that the allocator. // If thread 1 reallocs some memory which frees address A and then thread 2 reallocs and gets address A // the memory debugger will report incorrect double-alloc errors if it sees thread 2's realloc before the thread 1's free. // This solution is not ideal because we will report free+alloc to the memory debugger even if the existing allocation // was just resized in place. // The other way to fix this would be to hold the memory debugger lock for the whole duration of the realloc // to prevent the debugger from seeing events in a different order than the allocator. But this will probably slow down // allocations too much in a contended multi-threaded scenario, so I opted for this solution. // -rluba, 2026-06-19 #if MEMORY_DEBUGGER Memory_Debugger.check_free(a, memory); result := a.proc(.RESIZE, size, old_size, memory, a.data); #if MEMORY_DEBUGGER Memory_Debugger.check_alloc(a, result, size); return result; } create_heap :: (a: Allocator) -> Allocator { b_data := a.proc(.CREATE_HEAP, 0, 0, null, a.data); b := Allocator.{a.proc, b_data}; #if MEMORY_DEBUGGER Memory_Debugger.check_create_heap(b); return b; } destroy_heap :: (b: Allocator) { #if MEMORY_DEBUGGER Memory_Debugger.check_destroy_heap(b); b.proc(.DESTROY_HEAP, 0, 0, null, b.data); } get_capabilities :: (b: Allocator) -> (caps: Allocator_Caps, name: string) { name: string; result := cast,no_check(Allocator_Caps)b.proc(.CAPS, 0, 0, *name, null); return result, name; } // Allocates a string. alloc_string :: (count: int) -> string { assert(count >= 0); if !count return ""; s: string = ---; s.data = alloc(count); s.count = count; return s; } talloc_string :: (count: int) -> string { // This is kind of silly now; we might deprecate it. return inline alloc_string(count,, temp); } string_free :: inline (s: string) { free(s.data); } free :: inline (s: string) { free(s.data); } push_allocator :: (allocator: Allocator) #expand #no_debug { // This should probably be in caps or something, to tell people it's a macro. old_allocator := context.allocator; context.allocator = allocator; `defer context.allocator = old_allocator; } push_allocator :: (proc: Allocator_Proc, data: *void) #expand #no_debug { old_allocator := context.allocator; context.allocator.proc = proc; context.allocator.data = data; `defer context.allocator = old_allocator; } // Should get_field not be in Basic? Do we use this any more? get_field :: (info: *Type_Info_Struct, name: string) -> *Type_Info_Struct_Member, offset_in_bytes: s64 { // First pass: Don't try recursing. for * info.members { if name == it.name return it, it.offset_in_bytes; } // Second pass: See if we can get something recursively. for * info.members { if (it.flags & .USING) && (it.type.type == .STRUCT) { result, child_offset := get_field(cast(*Type_Info_Struct)it.type, name); if result return result, it.offset_in_bytes + child_offset; } } return null, 0; } min :: (a: $T, b: T) -> T { // Return the smallest of (a, b). if a < b return a; return b; } max :: (a: $T, b: T) -> T { // Return the largest of (a, b). if b < a return a; return b; } // In case you shadow 'min' and 'max' with local variables, provide capitalized versions...? // I had originally made these capitalized until someone passive-aggressively changed them. // Change them back? -jblow, 2 June 2021. Min :: min; Max :: max; IsScalar :: (T: Type) -> bool #compile_time { // This is a utility used in the #modify routines for max, min, clamp. ti := cast(*Type_Info) T; if ti.type == { // These are fine: case .INTEGER; #through; case .FLOAT; #through; case .POINTER; #through; case .ENUM; return true; } return false; } max :: inline (a: $T, x: ..T) -> T #modify { return IsScalar(T), "Argument is not of a scalar type."; } { m := a; for x if m < it m = it; return m; } min :: inline (a: $T, x: ..T) -> T #modify { return IsScalar(T), "Argument is not of a scalar type."; } { m := a; for x if it < m m = it; return m; } clamp :: (x: $T, a: T, b: T) -> T #modify { return IsScalar(T), "Argument is not of a scalar type."; } { if x < a x = a; if x > b x = b; return x; } swap :: inline (a: *$T, b: *T) { // This is for when only want to mention each expression once instead of twice, because the names are long... a.*, b.* = b.*, a.*; } // @Cleanup: We would like to do: // // talloc :: temporary_alloc; // // But this makes the code live right now. When we fix this part of the // dead code elimination, we can do that... talloc :: inline (size: s64) -> *void { return temporary_allocator_proc(.ALLOCATE, size, 0, null, null); } temporary_alloc :: inline (size: s64) -> *void { return temporary_allocator_proc(.ALLOCATE, size, 0, null, null); } temp :: Allocator.{temporary_allocator_proc, null}; temporary_allocator :: temp; reset_temporary_storage :: () { ts := context.temporary_storage; if ts reset_temporary_storage(ts); } round :: (f: float32) -> s32 #expand { return cast(s32) floor(f+0.5); } reset_struct :: Initialize; Initialize :: (memory: *$T) { ini :: initializer_of(T); #if ini inline ini(memory); else memset(memory, 0, size_of(T)); } New :: ($T: Type, $initialized := true) -> *T { memory := alloc(size_of(T)); // Since New gets called a lot, we just roll Initialize() into here, so that // we don't kick off so many extra polymorphs. #if initialized { ini :: initializer_of(T); #if ini inline ini(memory); else memset(memory, 0, size_of(T)); } return cast(*T) memory; } NewArray :: (count: s64, $T: Type, $initialized := true, alignment: s32 = -1) -> (result: [] T, unaligned_base: *void) { if !count return .[], null; // NewArray is for when we want to new a not-known-at-compile-time // number of things... a := context.allocator; extra: s32 = 0; if alignment > 0 { extra = alignment; } // 'extra' lets us allocate extra memory so that the user can bump the pointer forward // for alignment purposes. // This is more versatile than requiring all allocators to support wacky alignments. // This doesn't mean it's the best solution, but it is what we are doing for now. original_memory := cast(*void) alloc(size_of(T) * count + extra); if !original_memory return .[], null; memory: *void; if alignment > 0 { memory = cast(*void) align_forward(cast(s64)original_memory, alignment); } else { memory = original_memory; } result: [] T = ---; result.count = count; result.data = memory; #if initialized { ini :: initializer_of(T); #if ini { for 0..count-1 { inline ini(memory); memory += size_of(T); } } else { memset(memory, 0, count * size_of(T)); } } // If alignment == -1, you don't need to think about original_memory; you can just ignore it. // You only need to care if alignment != -1; in that case, when it comes time to free the memory, // you should free original_memory, not result.data. return result, original_memory; } decimal_digits :: (v_in: s32) -> s32 { v := v_in; if (v < 0) v = -v; // ignore sign if (v < 10) return 1; if (v < 100) return 2; if (v < 1000) return 3; if (v < 10000) return 4; if (v < 100000) return 5; if (v < 1000000) return 6; if (v < 10000000) return 7; if (v < 100000000) return 8; if (v < 1000000000) return 9; return 10; // max for s32 (2,147,483,648) } // align_forward adds to orig_size so that it becomes an even multiple of 'alignment'. align_forward :: (orig_size: s64, alignment: s64) -> s64 { a := alignment; assert(a > 0); size := ((orig_size + a - 1) / a) * a; // The above code might be hard to think about. Here's another implementation, // that has a branch: // size := orig_size; // remainder := orig_size % a; // if remainder size += a - remainder; return size; } #if OS == { case .WINDOWS; #load "windows.jai"; case .LINUX; #load "linux.jai"; case .ANDROID; #load "linux.jai"; case .MACOS; #load "osx.jai"; case .PS5; #load "ps5.jai"; case .NN_SWITCH; #load "nintendo.jai"; case .NN_SWITCH2; #load "nintendo.jai"; } // pack_stack_trace is convenient in case you want to store the stack trace // on a data structure. You can't just store a pointer to context.stack_trace, // because that data will be destroyed as the stack unwinds. So this routine // copies that trace into a packed array. pack_stack_trace :: () -> [] Stack_Trace_Node { return pack_stack_trace(context.stack_trace); } pack_stack_trace :: (node: *Stack_Trace_Node) -> [] Stack_Trace_Node { // We could do a two-pass version where we array_reserve the right amount of data...! result: [..] Stack_Trace_Node; while node { array_add(*result, node.*); node = node.next; } // Fix up the 'next' pointers so you can use them normally. for * result { if it.next // The one that was already null, at the end, leave it null. Otherwise: it.next = it + 1; // Set 'next' to point at the subsequent element in the array. } return result; } // // print_stack_trace() can be used as a low-level routine for people writing // Allocators, assertion handlers, loggers etc, and who are encountering // bugs that they want help with. This is guaranteed not to call into // any logger, allocator, etc because it uses only bottom-level routines. // // That said, you can also use it in other code! // // // Higher-level routines can use log_stack_trace(), or get_stack_trace_strings(), which // gives you strings you can render graphically, etc. // print_stack_trace :: (node: *Stack_Trace_Node, prefix: string = "", to_standard_error := true) { while node { if node.info { if prefix.count write_string(prefix, to_standard_error = to_standard_error); write_string("'", to_standard_error = to_standard_error); if node.info.name write_string(node.info.name, to_standard_error = to_standard_error); write_string("' at ", to_standard_error = to_standard_error); if node.info.location.fully_pathed_filename write_string(node.info.location.fully_pathed_filename, to_standard_error = to_standard_error); write_string(":", to_standard_error = to_standard_error); write_nonnegative_number(node.line_number, to_standard_error = to_standard_error); write_string("\n", to_standard_error = to_standard_error); } node = node.next; } } log_stack_trace :: (node: *Stack_Trace_Node = null, log_flags := Log_Flags.ERROR, prefix := "") { strings := get_stack_trace_strings(node); for strings log("%1%2\n", prefix, it, flags = log_flags); } get_stack_trace_strings :: (node: *Stack_Trace_Node = null) -> [..] string /* Temporary_Storage */ { if !node node = context.stack_trace; results: [..] string; results.allocator = temp; while node { if node.info { s := sprint("'%' at %:%", node.info.name, node.info.location.fully_pathed_filename, node.line_number,, allocator=temp); array_add(*results, s); } node = node.next; } return results; } remember_allocators :: (data: *$T) { // In this language, we want the user of a function or library to be able to // control that function's memory allocation using context.allocator. // // When you make a data structure that needs to allocate memory over a long // period of time, though, this introduces a potential annoying constraint: // if the user wants to store that data structure or pass it to some other code, // that the user did not write, that other code will not know what allocator to push. // So it's a good idea for data structures like this to store allocators at // initialization time. (Data structures like Hash_Table and Bucket_Array do this; // this is also why the language-native [..] array has allocator and allocator_data // slots.) // // To make it convenient to save allocators in this way, we provide this function; // it's a very simple function, but it helps people remember and continue the convention. // All you need is to have 'allocator' and 'allocator_data' members, then call // this routine. if context.allocator.proc { data.allocator = context.allocator; } else { data.allocator = context.default_allocator; } } resizable :: (a: [] $T, set_allocators := true) -> [..] T { // Convert a [] to a [..] that owns the same memory. // The [..]'s allocated pointer will be set to a.count, so it will // reallocate if you try to add any more. // Since there is no way to know what the allocator of the [] was, // we assume it's the current allocator, and remember that, unless you // say set_allocators=false. result: [..] T = ---; result.count = a.count; result.allocated = a.count; result.data = a.data; if set_allocators remember_allocators(*result); return result; } get_integer_range :: ($T: Type) -> (low: T, high: T) { // Returns the lowest and highest values for a given integer type. // For example, u8 returns (0, 255); s8 returns (-127, 128). IS_INTEGER, SIGNED, BITS :: #run is_integer_type(T); #assert(IS_INTEGER); #if SIGNED { low := (cast(T)1) << (BITS-1); } else { low: T = 0; } return low, ~low; } clamp_to_another_integer_type :: (value: $SourceType, $TargetType: Type) -> TargetType { // SourceType and TargetType SOURCE_INTEGER, SOURCE_SIGNED, SOURCE_BITS :: #run is_integer_type(SourceType); TARGET_INTEGER, TARGET_SIGNED, TARGET_BITS :: #run is_integer_type(TargetType); #assert SOURCE_INTEGER && TARGET_INTEGER; LOW, HIGH :: #run get_integer_range(TargetType); #if SOURCE_SIGNED == TARGET_SIGNED { // Both are signed, or both are unsigned. #if TARGET_BITS < SOURCE_BITS { if value < LOW value = LOW; if value > HIGH value = HIGH; } return cast,no_check(TargetType) value; } else { // If we get here, SOURCE_SIGNED != TARGET_SIGNED. #if SOURCE_SIGNED { // TARGET_SIGNED == false. if value < 0 value = 0; #if TARGET_BITS < SOURCE_BITS { if value > HIGH value = HIGH; } } else { // SOURCE_SIGNED == false, TARGET_SIGNED == true. #if TARGET_BITS <= SOURCE_BITS { if value > HIGH value = HIGH; } } return cast,no_check(TargetType) value; } } is_integer_type :: ($T: Type) -> (is_integer: bool, is_signed: bool, bits: u8) { info := type_info(T); if info.type == .INTEGER { info_int := cast(*Type_Info_Integer) info; return true, info_int.signed, size_of(T)*8; } return false, false, 0; } seconds_since_init :: () -> float64 { // This is the replacement for seconds_since_init(). one_time_init(*is_get_seconds_initialized, init_get_seconds()); delta := current_time_monotonic() - get_seconds_base_time; return to_float64_seconds(delta); } #scope_file is_get_seconds_initialized: s32; get_seconds_base_time: Apollo_Time; init_get_seconds :: () { get_seconds_base_time = current_time_monotonic(); } #scope_export // Ok, so for many years kilo, mega and gigabytes meant 1024 * ..., but now // we're supposed to use kibi, mibi and gibi?? Bytes :: (b: s64) -> s64 #expand { return b; } KB :: Kilobytes; Kilobytes :: (count: s64) -> s64 #expand { return count * 1024; } MB :: Megabytes; Megabytes :: (count: s64) -> s64 #expand { return count * 1_048_576; } GB :: Gigabytes; Gigabytes :: (count: s64) -> s64 #expand { return count * 1_073_741_824; } TB :: Terabytes; Terabytes :: (count: s64) -> s64 #expand { return count * 1_099_511_627_776; } format_time_milliseconds :: (time: float64) -> string { return tprint("% ms", FF(time * 1.0e3, 3)); } format_delta_seconds :: inline (end_time: Apollo_Time, start_time: Apollo_Time) -> string { return format_time_seconds(to_float64_seconds(end_time-start_time)); } format_time_seconds :: (time: float64) -> string { if time < 1.0e-6 then return tprint("% ns", FF(time * 1.0e9, 2)); if time < 1.0e-3 then return tprint("% us", FF(time * 1.0e6, 3)); if time < 1 then return tprint("% ms", FF(time * 1.0e3, 3)); return tprint("% s", FF(time, 2)); } format_count :: (count: $T) -> string { units : []string = .["", "thousand", "million", "billion", "trillion"]; count_f64 := cast(float64)count; unit_index := 0; while count_f64 >= 1000 && unit_index < (units.count - 1) { count_f64 /= 1000.0; unit_index += 1; } return tprint("% %", FF(count_f64, 2), units[unit_index]); } format_cycles :: (ticks: $T) -> string { units : []string = .["cycles", "K cycles", "M cycles", "B cycles", "T cycles"]; count_f64 := cast(float64)ticks; unit_index := 0; while count_f64 >= 1000 && unit_index < (units.count - 1) { count_f64 /= 1000.0; unit_index += 1; } return tprint("% %", FF(count_f64, 2), units[unit_index]); } format_bytes :: (bytes_count: int) -> string { units : []string = .["B", "KB", "MB", "GB", "TB", "PB"]; count_f64 := cast(float64)bytes_count; unit_index := 0; // Scale down until size < 1024 or we run out of units while count_f64 >= 1024 && unit_index < (units.count - 1) { count_f64 /= 1024.0; unit_index += 1; } return tprint("% %", FF(count_f64, 2), units[unit_index]); }