// This is the runtime code required by the compiler // IMPORTANT NOTE(bill): Do not change the order of any of this data // The compiler relies upon this _exact_ order package runtime import "core:os" import "intrinsics" _ :: intrinsics; // Naming Conventions: // In general, Ada_Case for types and snake_case for values // // Package Name: snake_case (but prefer single word) // Import Name: snake_case (but prefer single word) // Types: Ada_Case // Enum Values: Ada_Case // Procedures: snake_case // Local Variables: snake_case // Constant Variables: SCREAMING_SNAKE_CASE // IMPORTANT NOTE(bill): `type_info_of` cannot be used within a // #shared_global_scope due to the internals of the compiler. // This could change at a later date if the all these data structures are // implemented within the compiler rather than in this "preload" file // NOTE(bill): This must match the compiler's Calling_Convention :: enum u8 { Invalid = 0, Odin = 1, Contextless = 2, C = 3, Std = 4, Fast = 5, } Type_Info_Enum_Value :: distinct i64; Platform_Endianness :: enum u8 { Platform = 0, Little = 1, Big = 2, } Type_Info_Struct_Soa_Kind :: enum u8 { None = 0, Fixed = 1, Slice = 2, Dynamic = 3, } // Variant Types Type_Info_Named :: struct {name: string, base: ^Type_Info}; Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness}; Type_Info_Rune :: struct {}; Type_Info_Float :: struct {endianness: Platform_Endianness}; Type_Info_Complex :: struct {}; Type_Info_Quaternion :: struct {}; Type_Info_String :: struct {is_cstring: bool}; Type_Info_Boolean :: struct {}; Type_Info_Any :: struct {}; Type_Info_Type_Id :: struct {}; Type_Info_Pointer :: struct { elem: ^Type_Info // nil -> rawptr }; Type_Info_Procedure :: struct { params: ^Type_Info, // Type_Info_Tuple results: ^Type_Info, // Type_Info_Tuple variadic: bool, convention: Calling_Convention, }; Type_Info_Array :: struct { elem: ^Type_Info, elem_size: int, count: int, }; Type_Info_Enumerated_Array :: struct { elem: ^Type_Info, index: ^Type_Info, elem_size: int, count: int, min_value: Type_Info_Enum_Value, max_value: Type_Info_Enum_Value, }; Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int}; Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int}; Type_Info_Tuple :: struct { // Only really used for procedures types: []^Type_Info, names: []string, }; Type_Info_Struct :: struct { types: []^Type_Info, names: []string, offsets: []uintptr, usings: []bool, tags: []string, is_packed: bool, is_raw_union: bool, custom_align: bool, // These are only set iff this structure is an SOA structure soa_kind: Type_Info_Struct_Soa_Kind, soa_base_type: ^Type_Info, soa_len: int, }; Type_Info_Union :: struct { variants: []^Type_Info, tag_offset: uintptr, tag_type: ^Type_Info, custom_align: bool, no_nil: bool, maybe: bool, }; Type_Info_Enum :: struct { base: ^Type_Info, names: []string, values: []Type_Info_Enum_Value, }; Type_Info_Map :: struct { key: ^Type_Info, value: ^Type_Info, generated_struct: ^Type_Info, }; Type_Info_Bit_Field :: struct { names: []string, bits: []i32, offsets: []i32, }; Type_Info_Bit_Set :: struct { elem: ^Type_Info, underlying: ^Type_Info, // Possibly nil lower: i64, upper: i64, }; Type_Info_Opaque :: struct { elem: ^Type_Info, }; Type_Info_Simd_Vector :: struct { elem: ^Type_Info, elem_size: int, count: int, is_x86_mmx: bool, }; Type_Info_Relative_Pointer :: struct { pointer: ^Type_Info, base_integer: ^Type_Info, }; Type_Info_Relative_Slice :: struct { slice: ^Type_Info, base_integer: ^Type_Info, }; Type_Info :: struct { size: int, align: int, id: typeid, variant: union { Type_Info_Named, Type_Info_Integer, Type_Info_Rune, Type_Info_Float, Type_Info_Complex, Type_Info_Quaternion, Type_Info_String, Type_Info_Boolean, Type_Info_Any, Type_Info_Type_Id, Type_Info_Pointer, Type_Info_Procedure, Type_Info_Array, Type_Info_Enumerated_Array, Type_Info_Dynamic_Array, Type_Info_Slice, Type_Info_Tuple, Type_Info_Struct, Type_Info_Union, Type_Info_Enum, Type_Info_Map, Type_Info_Bit_Field, Type_Info_Bit_Set, Type_Info_Opaque, Type_Info_Simd_Vector, Type_Info_Relative_Pointer, Type_Info_Relative_Slice, }, } // NOTE(bill): This must match the compiler's Typeid_Kind :: enum u8 { Invalid, Integer, Rune, Float, Complex, Quaternion, String, Boolean, Any, Type_Id, Pointer, Procedure, Array, Enumerated_Array, Dynamic_Array, Slice, Tuple, Struct, Union, Enum, Map, Bit_Field, Bit_Set, Opaque, Simd_Vector, Relative_Pointer, Relative_Slice, } #assert(len(Typeid_Kind) < 32); Typeid_Bit_Field :: bit_field #align align_of(uintptr) { index: 8*size_of(uintptr) - 8, kind: 5, // Typeid_Kind named: 1, special: 1, // signed, cstring, etc reserved: 1, } #assert(size_of(Typeid_Bit_Field) == size_of(uintptr)); // NOTE(bill): only the ones that are needed (not all types) // This will be set by the compiler type_table: []Type_Info; args__: []cstring; // IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it) @builtin Maybe :: union(T: typeid) #maybe {T}; Source_Code_Location :: struct { file_path: string, line, column: int, procedure: string, hash: u64, } Assertion_Failure_Proc :: #type proc(prefix, message: string, loc: Source_Code_Location); // Allocation Stuff Allocator_Mode :: enum byte { Alloc, Free, Free_All, Resize, Query_Features, Query_Info, } Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; Allocator_Query_Info :: struct { pointer: rawptr, size: Maybe(int), alignment: Maybe(int), } Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, flags: u64 = 0, location: Source_Code_Location = #caller_location) -> rawptr; Allocator :: struct { procedure: Allocator_Proc, data: rawptr, } // Logging stuff Logger_Level :: enum uint { Debug = 0, Info = 10, Warning = 20, Error = 30, Fatal = 40, } Logger_Option :: enum { Level, Date, Time, Short_File_Path, Long_File_Path, Line, Procedure, Terminal_Color, Thread_Id } Logger_Options :: bit_set[Logger_Option]; Logger_Proc :: #type proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location); Logger :: struct { procedure: Logger_Proc, data: rawptr, lowest_level: Logger_Level, options: Logger_Options, } Context :: struct { allocator: Allocator, temp_allocator: Allocator, assertion_failure_proc: Assertion_Failure_Proc, logger: Logger, thread_id: int, user_data: any, user_ptr: rawptr, user_index: int, } @thread_local global_default_temp_allocator_data: Default_Temp_Allocator; Raw_String :: struct { data: ^byte, len: int, } Raw_Slice :: struct { data: rawptr, len: int, } Raw_Dynamic_Array :: struct { data: rawptr, len: int, cap: int, allocator: Allocator, } Raw_Map :: struct { hashes: []int, entries: Raw_Dynamic_Array, } INITIAL_MAP_CAP :: 16; Map_Key :: struct { hash: u64, str: string, } Map_Find_Result :: struct { hash_index: int, entry_prev: int, entry_index: int, } Map_Entry_Header :: struct { key: Map_Key, next: int, /* value: Value_Type, */ } Map_Header :: struct { m: ^Raw_Map, is_key_string: bool, entry_size: int, entry_align: int, value_offset: uintptr, value_size: int, } type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { if info == nil do return nil; base := info; loop: for { #partial switch i in base.variant { case Type_Info_Named: base = i.base; case: break loop; } } return base; } type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { if info == nil do return nil; base := info; loop: for { #partial switch i in base.variant { case Type_Info_Named: base = i.base; case Type_Info_Enum: base = i.base; case Type_Info_Opaque: base = i.elem; case: break loop; } } return base; } type_info_base_without_enum :: type_info_core; __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info { data := transmute(Typeid_Bit_Field)id; n := int(data.index); if n < 0 || n >= len(type_table) { n = 0; } return &type_table[n]; } typeid_base :: proc "contextless" (id: typeid) -> typeid { ti := type_info_of(id); ti = type_info_base(ti); return ti.id; } typeid_core :: proc "contextless" (id: typeid) -> typeid { ti := type_info_base_without_enum(type_info_of(id)); return ti.id; } typeid_base_without_enum :: typeid_core; @(default_calling_convention = "none") foreign { @(link_name="llvm.assume") assume :: proc(cond: bool) ---; @(link_name="llvm.debugtrap") debug_trap :: proc() ---; @(link_name="llvm.trap") trap :: proc() -> ! ---; @(link_name="llvm.readcyclecounter") read_cycle_counter :: proc() -> u64 ---; } default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location) { // Do nothing } default_logger :: proc() -> Logger { return Logger{default_logger_proc, nil, Logger_Level.Debug, nil}; } default_context :: proc "contextless" () -> Context { c: Context; __init_context(&c); return c; } @private __init_context_from_ptr :: proc "contextless" (c: ^Context, other: ^Context) { if c == nil do return; c^ = other^; __init_context(c); } @private __init_context :: proc "contextless" (c: ^Context) { if c == nil do return; // NOTE(bill): Do not initialize these procedures with a call as they are not defined with the "contexless" calling convention c.allocator.procedure = default_allocator_proc; c.allocator.data = nil; c.temp_allocator.procedure = default_temp_allocator_proc; c.temp_allocator.data = &global_default_temp_allocator_data; c.thread_id = os.current_thread_id(); // NOTE(bill): This is "contextless" so it is okay to call c.assertion_failure_proc = default_assertion_failure_proc; c.logger.procedure = default_logger_proc; c.logger.data = nil; // c.stdin = os.stdin; // c.stdout = os.stdout; // c.stderr = os.stderr; } @builtin init_global_temporary_allocator :: proc(data: []byte, backup_allocator := context.allocator) { default_temp_allocator_init(&global_default_temp_allocator_data, data, backup_allocator); } default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) { fd := os.stderr; print_caller_location(fd, loc); os.write_string(fd, " "); os.write_string(fd, prefix); if len(message) > 0 { os.write_string(fd, ": "); os.write_string(fd, message); } os.write_byte(fd, '\n'); debug_trap(); } @builtin copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { n := max(0, min(len(dst), len(src))); if n > 0 { mem_copy(raw_data(dst), raw_data(src), n*size_of(E)); } return n; } @builtin copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int { n := max(0, min(len(dst), len(src))); if n > 0 { mem_copy(raw_data(dst), raw_data(src), n); } return n; } @builtin copy :: proc{copy_slice, copy_from_string}; @builtin unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) { bounds_check_error_loc(loc, index, len(array)); n := len(array)-1; if index != n { array[index] = array[n]; } pop(array); } @builtin ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) { bounds_check_error_loc(loc, index, len(array)); if index+1 < len(array) { copy(array[index:], array[index+1:]); } pop(array); } @builtin pop :: proc(array: ^$T/[dynamic]$E) -> E { if len(array) == 0 { return E{}; } assert(len(array) > 0); res := #no_bounds_check array[len(array)-1]; (^Raw_Dynamic_Array)(array).len -= 1; return res; } @builtin pop_safe :: proc(array: ^$T/[dynamic]$E) -> (E, bool) { if len(array) == 0 { return E{}, false; } res := #no_bounds_check array[len(array)-1]; (^Raw_Dynamic_Array)(array).len -= 1; return res, true; } @builtin pop_front :: proc(array: ^$T/[dynamic]$E) -> E #no_bounds_check { assert(len(array) > 0); res := array[0]; if len(array) > 1 { copy(array[0:], array[1:]); } (^Raw_Dynamic_Array)(array).len -= 1; return res; } @builtin pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (E, bool) #no_bounds_check { if len(array) == 0 { return E{}, false; } res := array[0]; if len(array) > 1 { copy(array[0:], array[1:]); } (^Raw_Dynamic_Array)(array).len -= 1; return res, true; } @builtin clear :: proc{clear_dynamic_array, clear_map}; @builtin reserve :: proc{reserve_dynamic_array, reserve_map}; @builtin resize :: proc{resize_dynamic_array}; @builtin free :: proc{mem_free}; @builtin free_all :: proc{mem_free_all}; @builtin delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) { mem_free(raw_data(str), allocator, loc); } @builtin delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) { mem_free((^byte)(str), allocator, loc); } @builtin delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) { mem_free(raw_data(array), array.allocator, loc); } @builtin delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) { mem_free(raw_data(array), allocator, loc); } @builtin delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) { raw := transmute(Raw_Map)m; delete_slice(raw.hashes); mem_free(raw.entries.data, raw.entries.allocator, loc); } @builtin delete :: proc{ delete_string, delete_cstring, delete_dynamic_array, delete_slice, delete_map, }; @builtin new :: inline proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> ^T { ptr := (^T)(mem_alloc(size_of(T), align_of(T), allocator, loc)); if ptr != nil do ptr^ = T{}; return ptr; } @builtin new_clone :: inline proc(data: $T, allocator := context.allocator, loc := #caller_location) -> ^T { ptr := (^T)(mem_alloc(size_of(T), align_of(T), allocator, loc)); if ptr != nil do ptr^ = data; return ptr; } make_aligned :: proc($T: typeid/[]$E, auto_cast len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> T { make_slice_error_loc(loc, len); data := mem_alloc(size_of(E)*len, alignment, allocator, loc); if data == nil && size_of(E) != 0 { return nil; } // mem_zero(data, size_of(E)*len); s := Raw_Slice{data, len}; return transmute(T)s; } @builtin make_slice :: inline proc($T: typeid/[]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T { return make_aligned(T, len, align_of(E), allocator, loc); } @builtin make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> T { return make_dynamic_array_len_cap(T, 0, 16, allocator, loc); } @builtin make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, auto_cast len: int, allocator := context.allocator, loc := #caller_location) -> T { return make_dynamic_array_len_cap(T, len, len, allocator, loc); } @builtin make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, auto_cast len: int, auto_cast cap: int, allocator := context.allocator, loc := #caller_location) -> T { make_dynamic_array_error_loc(loc, len, cap); data := mem_alloc(size_of(E)*cap, align_of(E), allocator, loc); s := Raw_Dynamic_Array{data, len, cap, allocator}; if data == nil && size_of(E) != 0 { s.len, s.cap = 0, 0; } // mem_zero(data, size_of(E)*cap); return transmute(T)s; } @builtin make_map :: proc($T: typeid/map[$K]$E, auto_cast cap: int = 16, allocator := context.allocator, loc := #caller_location) -> T { make_map_expr_error_loc(loc, cap); context.allocator = allocator; m: T; reserve_map(&m, cap); return m; } @builtin make :: proc{ make_slice, make_dynamic_array, make_dynamic_array_len, make_dynamic_array_len_cap, make_map, }; @builtin clear_map :: inline proc "contextless" (m: ^$T/map[$K]$V) { if m == nil do return; raw_map := (^Raw_Map)(m); entries := (^Raw_Dynamic_Array)(&raw_map.entries); entries.len = 0; for _, i in raw_map.hashes { raw_map.hashes[i] = -1; } } @builtin reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int) { if m != nil do __dynamic_map_reserve(__get_map_header(m), capacity); } @builtin delete_key :: proc(m: ^$T/map[$K]$V, key: K) { if m != nil do __dynamic_map_delete_key(__get_map_header(m), __get_map_key(key)); } @builtin append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) { if array == nil do return; arg_len := 1; if cap(array) < len(array)+arg_len { cap := 2 * cap(array) + max(8, arg_len); _ = reserve(array, cap, loc); } arg_len = min(cap(array)-len(array), arg_len); if arg_len > 0 { a := (^Raw_Dynamic_Array)(array); if size_of(E) != 0 { data := (^E)(a.data); assert(data != nil); val := arg; mem_copy(ptr_offset(data, a.len), &val, size_of(E)); } a.len += arg_len; } } @builtin append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) { if array == nil do return; arg_len := len(args); if arg_len <= 0 do return; if cap(array) < len(array)+arg_len { cap := 2 * cap(array) + max(8, arg_len); _ = reserve(array, cap, loc); } arg_len = min(cap(array)-len(array), arg_len); if arg_len > 0 { a := (^Raw_Dynamic_Array)(array); if size_of(E) != 0 { data := (^E)(a.data); assert(data != nil); mem_copy(ptr_offset(data, a.len), &args[0], size_of(E) * arg_len); } a.len += arg_len; } } @builtin append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) { args := transmute([]E)arg; append_elems(array=array, args=args, loc=loc); } @builtin reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> bool { if array == nil do return false; old_cap := cap(array); if capacity <= old_cap do return true; if array.allocator.procedure == nil { array.allocator = context.allocator; } assert(array.allocator.procedure != nil); ti := type_info_of(typeid_of(T)); ti = type_info_base(ti); si := &ti.variant.(Type_Info_Struct); field_count := uintptr(len(si.offsets) - 3); if field_count == 0 { return true; } cap_ptr := cast(^int)rawptr(uintptr(array) + (field_count + 1)*size_of(rawptr)); assert(cap_ptr^ == old_cap); old_size := 0; new_size := 0; max_align := 0; for i in 0.. 0 { ti := type_info_of(typeid_of(T)); ti = type_info_base(ti); si := &ti.variant.(Type_Info_Struct); field_count := uintptr(len(si.offsets) - 3); if field_count == 0 { return; } data := (^rawptr)(array)^; len_ptr := cast(^int)rawptr(uintptr(array) + (field_count + 0)*size_of(rawptr)); soa_offset := 0; item_offset := 0; arg_copy := arg; arg_ptr := &arg_copy; max_align := 0; for i in 0.. 0 { ti := type_info_of(typeid_of(T)); ti = type_info_base(ti); si := &ti.variant.(Type_Info_Struct); field_count := uintptr(len(si.offsets) - 3); if field_count == 0 { return; } data := (^rawptr)(array)^; len_ptr := cast(^int)rawptr(uintptr(array) + (field_count + 0)*size_of(rawptr)); soa_offset := 0; item_offset := 0; args_ptr := &args[0]; max_align := 0; for i in 0.. bool { if array == nil do return false; a := (^Raw_Dynamic_Array)(array); if capacity <= a.cap do return true; if a.allocator.procedure == nil { a.allocator = context.allocator; } assert(a.allocator.procedure != nil); old_size := a.cap * size_of(E); new_size := capacity * size_of(E); allocator := a.allocator; new_data := allocator.procedure( allocator.data, .Resize, new_size, align_of(E), a.data, old_size, 0, loc, ); if new_data == nil do return false; a.data = new_data; a.cap = capacity; return true; } @builtin resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> bool { if array == nil do return false; a := (^Raw_Dynamic_Array)(array); if length <= a.cap { a.len = max(length, 0); return true; } if a.allocator.procedure == nil { a.allocator = context.allocator; } assert(a.allocator.procedure != nil); old_size := a.cap * size_of(E); new_size := length * size_of(E); allocator := a.allocator; new_data := allocator.procedure( allocator.data, .Resize, new_size, align_of(E), a.data, old_size, 0, loc, ); if new_data == nil do return false; a.data = new_data; a.len = length; a.cap = length; return true; } @builtin incl_elem :: inline proc(s: ^$S/bit_set[$E; $U], elem: E) -> S { s^ |= {elem}; return s^; } @builtin incl_elems :: inline proc(s: ^$S/bit_set[$E; $U], elems: ..E) -> S { for elem in elems do s^ |= {elem}; return s^; } @builtin incl_bit_set :: inline proc(s: ^$S/bit_set[$E; $U], other: S) -> S { s^ |= other; return s^; } @builtin excl_elem :: inline proc(s: ^$S/bit_set[$E; $U], elem: E) -> S { s^ &~= {elem}; return s^; } @builtin excl_elems :: inline proc(s: ^$S/bit_set[$E; $U], elems: ..E) -> S { for elem in elems do s^ &~= {elem}; return s^; } @builtin excl_bit_set :: inline proc(s: ^$S/bit_set[$E; $U], other: S) -> S { s^ &~= other; return s^; } @builtin incl :: proc{incl_elem, incl_elems, incl_bit_set}; @builtin excl :: proc{excl_elem, excl_elems, excl_bit_set}; @builtin card :: proc(s: $S/bit_set[$E; $U]) -> int { when size_of(S) == 1 { foreign { @(link_name="llvm.ctpop.i8") count_ones :: proc(i: u8) -> u8 --- } return int(count_ones(transmute(u8)s)); } else when size_of(S) == 2 { foreign { @(link_name="llvm.ctpop.i16") count_ones :: proc(i: u16) -> u16 --- } return int(count_ones(transmute(u16)s)); } else when size_of(S) == 4 { foreign { @(link_name="llvm.ctpop.i32") count_ones :: proc(i: u32) -> u32 --- } return int(count_ones(transmute(u32)s)); } else when size_of(S) == 8 { foreign { @(link_name="llvm.ctpop.i64") count_ones :: proc(i: u64) -> u64 --- } return int(count_ones(transmute(u64)s)); } else when size_of(S) == 16 { foreign { @(link_name="llvm.ctpop.i128") count_ones :: proc(i: u128) -> u128 --- } return int(count_ones(transmute(u128)s)); } else { #assert(false); return 0; } } @builtin raw_array_data :: proc "contextless" (a: $P/^($T/[$N]$E)) -> ^E { return (^E)(a); } @builtin raw_slice_data :: proc "contextless" (s: $S/[]$E) -> ^E { ptr := (transmute(Raw_Slice)s).data; return (^E)(ptr); } @builtin raw_dynamic_array_data :: proc "contextless" (s: $S/[dynamic]$E) -> ^E { ptr := (transmute(Raw_Dynamic_Array)s).data; return (^E)(ptr); } @builtin raw_string_data :: proc "contextless" (s: $S/string) -> ^u8 { return (transmute(Raw_String)s).data; } @builtin raw_data :: proc{raw_array_data, raw_slice_data, raw_dynamic_array_data, raw_string_data}; @builtin @(disabled=ODIN_DISABLE_ASSERT) assert :: proc(condition: bool, message := "", loc := #caller_location) { if !condition { proc(message: string, loc: Source_Code_Location) { p := context.assertion_failure_proc; if p == nil { p = default_assertion_failure_proc; } p("runtime assertion", message, loc); }(message, loc); } } @builtin @(disabled=ODIN_DISABLE_ASSERT) panic :: proc(message: string, loc := #caller_location) -> ! { p := context.assertion_failure_proc; if p == nil { p = default_assertion_failure_proc; } p("panic", message, loc); } @builtin @(disabled=ODIN_DISABLE_ASSERT) unimplemented :: proc(message := "", loc := #caller_location) -> ! { p := context.assertion_failure_proc; if p == nil { p = default_assertion_failure_proc; } p("not yet implemented", message, loc); } @builtin @(disabled=ODIN_DISABLE_ASSERT) unreachable :: proc(message := "", loc := #caller_location) -> ! { p := context.assertion_failure_proc; if p == nil { p = default_assertion_failure_proc; } if message != "" { p("internal error", message, loc); } else { p("internal error", "entered unreachable code", loc); } } // Dynamic Array __dynamic_array_make :: proc(array_: rawptr, elem_size, elem_align: int, len, cap: int, loc := #caller_location) { array := (^Raw_Dynamic_Array)(array_); array.allocator = context.allocator; assert(array.allocator.procedure != nil); if cap > 0 { __dynamic_array_reserve(array_, elem_size, elem_align, cap, loc); array.len = len; } } __dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap: int, loc := #caller_location) -> bool { array := (^Raw_Dynamic_Array)(array_); // NOTE(tetra, 2020-01-26): We set the allocator before earlying-out below, because user code is usually written // assuming that appending/reserving will set the allocator, if it is not already set. if array.allocator.procedure == nil { array.allocator = context.allocator; } assert(array.allocator.procedure != nil); if cap <= array.cap do return true; old_size := array.cap * elem_size; new_size := cap * elem_size; allocator := array.allocator; new_data := allocator.procedure(allocator.data, .Resize, new_size, elem_align, array.data, old_size, 0, loc); if new_data != nil || elem_size == 0 { array.data = new_data; array.cap = cap; return true; } return false; } __dynamic_array_resize :: proc(array_: rawptr, elem_size, elem_align: int, len: int, loc := #caller_location) -> bool { array := (^Raw_Dynamic_Array)(array_); ok := __dynamic_array_reserve(array_, elem_size, elem_align, len, loc); if ok do array.len = len; return ok; } __dynamic_array_append :: proc(array_: rawptr, elem_size, elem_align: int, items: rawptr, item_count: int, loc := #caller_location) -> int { array := (^Raw_Dynamic_Array)(array_); if items == nil do return 0; if item_count <= 0 do return 0; ok := true; if array.cap <= array.len+item_count { cap := 2 * array.cap + max(8, item_count); ok = __dynamic_array_reserve(array, elem_size, elem_align, cap, loc); } // TODO(bill): Better error handling for failed reservation if !ok do return array.len; assert(array.data != nil); data := uintptr(array.data) + uintptr(elem_size*array.len); mem_copy(rawptr(data), items, elem_size * item_count); array.len += item_count; return array.len; } __dynamic_array_append_nothing :: proc(array_: rawptr, elem_size, elem_align: int, loc := #caller_location) -> int { array := (^Raw_Dynamic_Array)(array_); ok := true; if array.cap <= array.len+1 { cap := 2 * array.cap + max(8, 1); ok = __dynamic_array_reserve(array, elem_size, elem_align, cap, loc); } // TODO(bill): Better error handling for failed reservation if !ok do return array.len; assert(array.data != nil); data := uintptr(array.data) + uintptr(elem_size*array.len); mem_zero(rawptr(data), elem_size); array.len += 1; return array.len; } // Map __get_map_header :: proc "contextless" (m: ^$T/map[$K]$V) -> Map_Header { header := Map_Header{m = (^Raw_Map)(m)}; Entry :: struct { key: Map_Key, next: int, value: V, }; header.is_key_string = intrinsics.type_is_string(K); header.entry_size = int(size_of(Entry)); header.entry_align = int(align_of(Entry)); header.value_offset = uintptr(offset_of(Entry, value)); header.value_size = int(size_of(V)); return header; } __get_map_key :: proc "contextless" (k: $K) -> Map_Key { key := k; map_key: Map_Key; T :: intrinsics.type_core_type(K); when intrinsics.type_is_integer(T) { sz :: 8*size_of(T); when sz == 8 do map_key.hash = u64(( ^u8)(&key)^); else when sz == 16 do map_key.hash = u64((^u16)(&key)^); else when sz == 32 do map_key.hash = u64((^u32)(&key)^); else when sz == 64 do map_key.hash = u64((^u64)(&key)^); else do #assert(false, "Unhandled integer size"); } else when intrinsics.type_is_rune(T) { map_key.hash = u64((^rune)(&key)^); } else when intrinsics.type_is_pointer(T) { map_key.hash = u64(uintptr((^rawptr)(&key)^)); } else when intrinsics.type_is_float(T) { sz :: 8*size_of(T); when sz == 32 do map_key.hash = u64((^u32)(&key)^); else when sz == 64 do map_key.hash = u64((^u64)(&key)^); else do #assert(false, "Unhandled float size"); } else when intrinsics.type_is_string(T) { #assert(T == string); str := (^string)(&key)^; map_key.hash = default_hash_string(str); map_key.str = str; } else { #assert(false, "Unhandled map key type"); } return map_key; } _fnv64a :: proc "contextless" (data: []byte, seed: u64 = 0xcbf29ce484222325) -> u64 { h: u64 = seed; for b in data { h = (h ~ u64(b)) * 0x100000001b3; } return h; } default_hash :: proc "contextless" (data: []byte) -> u64 { return _fnv64a(data); } default_hash_string :: proc "contextless" (s: string) -> u64 do return default_hash(transmute([]byte)(s)); source_code_location_hash :: proc(s: Source_Code_Location) -> u64 { hash := _fnv64a(transmute([]byte)s.file_path); hash = hash ~ (u64(s.line) * 0x100000001b3); hash = hash ~ (u64(s.column) * 0x100000001b3); return hash; } __slice_resize :: proc(array_: ^$T/[]$E, new_count: int, allocator: Allocator, loc := #caller_location) -> bool { array := (^Raw_Slice)(array_); if new_count < array.len do return true; assert(allocator.procedure != nil); old_size := array.len*size_of(T); new_size := new_count*size_of(T); new_data := mem_resize(array.data, old_size, new_size, align_of(T), allocator, loc); if new_data == nil do return false; array.data = new_data; array.len = new_count; return true; } __dynamic_map_reserve :: proc(using header: Map_Header, cap: int, loc := #caller_location) { __dynamic_array_reserve(&m.entries, entry_size, entry_align, cap, loc); old_len := len(m.hashes); __slice_resize(&m.hashes, cap, m.entries.allocator, loc); for i in old_len.. rawptr { index := __dynamic_map_find(h, key).entry_index; if index >= 0 { data := uintptr(__dynamic_map_get_entry(h, index)); return rawptr(data + h.value_offset); } return nil; } __dynamic_map_set :: proc(h: Map_Header, key: Map_Key, value: rawptr, loc := #caller_location) #no_bounds_check { index: int; assert(value != nil); if len(h.m.hashes) == 0 { __dynamic_map_reserve(h, INITIAL_MAP_CAP, loc); __dynamic_map_grow(h, loc); } fr := __dynamic_map_find(h, key); if fr.entry_index >= 0 { index = fr.entry_index; } else { index = __dynamic_map_add_entry(h, key, loc); if fr.entry_prev >= 0 { entry := __dynamic_map_get_entry(h, fr.entry_prev); entry.next = index; } else { h.m.hashes[fr.hash_index] = index; } } { e := __dynamic_map_get_entry(h, index); e.key = key; val := (^byte)(uintptr(e) + h.value_offset); mem_copy(val, value, h.value_size); } if __dynamic_map_full(h) { __dynamic_map_grow(h, loc); } } __dynamic_map_grow :: proc(using h: Map_Header, loc := #caller_location) { // TODO(bill): Determine an efficient growing rate new_count := max(4*m.entries.cap + 7, INITIAL_MAP_CAP); __dynamic_map_rehash(h, new_count, loc); } __dynamic_map_full :: inline proc(using h: Map_Header) -> bool { return int(0.75 * f64(len(m.hashes))) <= m.entries.cap; } __dynamic_map_hash_equal :: proc(h: Map_Header, a, b: Map_Key) -> bool { if a.hash == b.hash { if h.is_key_string do return a.str == b.str; return true; } return false; } __dynamic_map_find :: proc(using h: Map_Header, key: Map_Key) -> Map_Find_Result #no_bounds_check { fr := Map_Find_Result{-1, -1, -1}; if n := u64(len(m.hashes)); n > 0 { fr.hash_index = int(key.hash % n); fr.entry_index = m.hashes[fr.hash_index]; for fr.entry_index >= 0 { entry := __dynamic_map_get_entry(h, fr.entry_index); if __dynamic_map_hash_equal(h, entry.key, key) do return fr; fr.entry_prev = fr.entry_index; fr.entry_index = entry.next; } } return fr; } __dynamic_map_add_entry :: proc(using h: Map_Header, key: Map_Key, loc := #caller_location) -> int { prev := m.entries.len; c := __dynamic_array_append_nothing(&m.entries, entry_size, entry_align, loc); if c != prev { end := __dynamic_map_get_entry(h, c-1); end.key = key; end.next = -1; } return prev; } __dynamic_map_delete_key :: proc(using h: Map_Header, key: Map_Key) { fr := __dynamic_map_find(h, key); if fr.entry_index >= 0 { __dynamic_map_erase(h, fr); } } __dynamic_map_get_entry :: proc(using h: Map_Header, index: int) -> ^Map_Entry_Header { assert(0 <= index && index < m.entries.len); return (^Map_Entry_Header)(uintptr(m.entries.data) + uintptr(index*entry_size)); } __dynamic_map_erase :: proc(using h: Map_Header, fr: Map_Find_Result) #no_bounds_check { if fr.entry_prev < 0 { m.hashes[fr.hash_index] = __dynamic_map_get_entry(h, fr.entry_index).next; } else { prev := __dynamic_map_get_entry(h, fr.entry_prev); curr := __dynamic_map_get_entry(h, fr.entry_index); prev.next = curr.next; } if (fr.entry_index == m.entries.len-1) { // NOTE(bill): No need to do anything else, just pop } else { old := __dynamic_map_get_entry(h, fr.entry_index); end := __dynamic_map_get_entry(h, m.entries.len-1); mem_copy(old, end, entry_size); if last := __dynamic_map_find(h, old.key); last.entry_prev >= 0 { last_entry := __dynamic_map_get_entry(h, last.entry_prev); last_entry.next = fr.entry_index; } else { m.hashes[last.hash_index] = fr.entry_index; } } // TODO(bill): Is this correct behaviour? m.entries.len -= 1; }