720 lines
24 KiB
Plaintext
720 lines
24 KiB
Plaintext
// 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]);
|
|
}
|
|
|