From 31914e9cb9f78965e329d70194241477ba00e511 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 26 Jan 2024 09:55:20 +0100 Subject: Add `odin test -all-packages` to be able to test an entire project --- src/build_settings.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index af518bcb4..926a9707f 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -422,6 +422,7 @@ struct BuildContext { Array extra_packages; StringSet test_names; + bool test_all_packages; gbAffinity affinity; isize thread_count; -- cgit v1.2.3 From 09fa1c29cd014b4560b3c79c72db68af20ef8187 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Jan 2024 21:05:53 +0000 Subject: Move `core:runtime` to `base:runtime`; keep alias around --- base/runtime/core.odin | 681 +++++++++++++++ base/runtime/core_builtin.odin | 915 ++++++++++++++++++++ base/runtime/core_builtin_matrix.odin | 274 ++++++ base/runtime/core_builtin_soa.odin | 428 ++++++++++ base/runtime/default_allocators_arena.odin | 304 +++++++ base/runtime/default_allocators_general.odin | 23 + base/runtime/default_allocators_js.odin | 5 + base/runtime/default_allocators_nil.odin | 88 ++ base/runtime/default_allocators_wasi.odin | 5 + base/runtime/default_allocators_windows.odin | 44 + base/runtime/default_temporary_allocator.odin | 79 ++ base/runtime/docs.odin | 179 ++++ base/runtime/dynamic_array_internal.odin | 138 +++ base/runtime/dynamic_map_internal.odin | 924 ++++++++++++++++++++ base/runtime/entry_unix.odin | 59 ++ base/runtime/entry_unix_no_crt_amd64.asm | 43 + base/runtime/entry_unix_no_crt_darwin_arm64.asm | 20 + base/runtime/entry_unix_no_crt_i386.asm | 18 + base/runtime/entry_wasm.odin | 20 + base/runtime/entry_windows.odin | 50 ++ base/runtime/error_checks.odin | 292 +++++++ base/runtime/internal.odin | 1036 +++++++++++++++++++++++ base/runtime/os_specific.odin | 7 + base/runtime/os_specific_any.odin | 16 + base/runtime/os_specific_darwin.odin | 12 + base/runtime/os_specific_freestanding.odin | 7 + base/runtime/os_specific_js.odin | 12 + base/runtime/os_specific_wasi.odin | 10 + base/runtime/os_specific_windows.odin | 135 +++ base/runtime/print.odin | 489 +++++++++++ base/runtime/procs.odin | 95 +++ base/runtime/procs_darwin.odin | 21 + base/runtime/procs_js.odin | 15 + base/runtime/procs_wasm.odin | 40 + base/runtime/procs_windows_amd64.asm | 79 ++ base/runtime/procs_windows_amd64.odin | 26 + base/runtime/procs_windows_i386.odin | 29 + base/runtime/udivmod128.odin | 156 ++++ core/runtime/core.odin | 681 --------------- core/runtime/core_builtin.odin | 915 -------------------- core/runtime/core_builtin_matrix.odin | 274 ------ core/runtime/core_builtin_soa.odin | 428 ---------- core/runtime/default_allocators_arena.odin | 304 ------- core/runtime/default_allocators_general.odin | 23 - core/runtime/default_allocators_js.odin | 5 - core/runtime/default_allocators_nil.odin | 88 -- core/runtime/default_allocators_wasi.odin | 5 - core/runtime/default_allocators_windows.odin | 44 - core/runtime/default_temporary_allocator.odin | 79 -- core/runtime/docs.odin | 179 ---- core/runtime/dynamic_array_internal.odin | 138 --- core/runtime/dynamic_map_internal.odin | 924 -------------------- core/runtime/entry_unix.odin | 59 -- core/runtime/entry_unix_no_crt_amd64.asm | 43 - core/runtime/entry_unix_no_crt_darwin_arm64.asm | 20 - core/runtime/entry_unix_no_crt_i386.asm | 18 - core/runtime/entry_wasm.odin | 20 - core/runtime/entry_windows.odin | 50 -- core/runtime/error_checks.odin | 292 ------- core/runtime/internal.odin | 1036 ----------------------- core/runtime/os_specific.odin | 7 - core/runtime/os_specific_any.odin | 16 - core/runtime/os_specific_darwin.odin | 12 - core/runtime/os_specific_freestanding.odin | 7 - core/runtime/os_specific_js.odin | 12 - core/runtime/os_specific_wasi.odin | 10 - core/runtime/os_specific_windows.odin | 135 --- core/runtime/print.odin | 489 ----------- core/runtime/procs.odin | 95 --- core/runtime/procs_darwin.odin | 21 - core/runtime/procs_js.odin | 15 - core/runtime/procs_wasm.odin | 40 - core/runtime/procs_windows_amd64.asm | 79 -- core/runtime/procs_windows_amd64.odin | 26 - core/runtime/procs_windows_i386.odin | 29 - core/runtime/udivmod128.odin | 156 ---- src/build_settings.cpp | 22 +- src/checker.cpp | 28 +- src/main.cpp | 1 + src/parser.cpp | 10 +- 80 files changed, 6828 insertions(+), 6781 deletions(-) create mode 100644 base/runtime/core.odin create mode 100644 base/runtime/core_builtin.odin create mode 100644 base/runtime/core_builtin_matrix.odin create mode 100644 base/runtime/core_builtin_soa.odin create mode 100644 base/runtime/default_allocators_arena.odin create mode 100644 base/runtime/default_allocators_general.odin create mode 100644 base/runtime/default_allocators_js.odin create mode 100644 base/runtime/default_allocators_nil.odin create mode 100644 base/runtime/default_allocators_wasi.odin create mode 100644 base/runtime/default_allocators_windows.odin create mode 100644 base/runtime/default_temporary_allocator.odin create mode 100644 base/runtime/docs.odin create mode 100644 base/runtime/dynamic_array_internal.odin create mode 100644 base/runtime/dynamic_map_internal.odin create mode 100644 base/runtime/entry_unix.odin create mode 100644 base/runtime/entry_unix_no_crt_amd64.asm create mode 100644 base/runtime/entry_unix_no_crt_darwin_arm64.asm create mode 100644 base/runtime/entry_unix_no_crt_i386.asm create mode 100644 base/runtime/entry_wasm.odin create mode 100644 base/runtime/entry_windows.odin create mode 100644 base/runtime/error_checks.odin create mode 100644 base/runtime/internal.odin create mode 100644 base/runtime/os_specific.odin create mode 100644 base/runtime/os_specific_any.odin create mode 100644 base/runtime/os_specific_darwin.odin create mode 100644 base/runtime/os_specific_freestanding.odin create mode 100644 base/runtime/os_specific_js.odin create mode 100644 base/runtime/os_specific_wasi.odin create mode 100644 base/runtime/os_specific_windows.odin create mode 100644 base/runtime/print.odin create mode 100644 base/runtime/procs.odin create mode 100644 base/runtime/procs_darwin.odin create mode 100644 base/runtime/procs_js.odin create mode 100644 base/runtime/procs_wasm.odin create mode 100644 base/runtime/procs_windows_amd64.asm create mode 100644 base/runtime/procs_windows_amd64.odin create mode 100644 base/runtime/procs_windows_i386.odin create mode 100644 base/runtime/udivmod128.odin delete mode 100644 core/runtime/core.odin delete mode 100644 core/runtime/core_builtin.odin delete mode 100644 core/runtime/core_builtin_matrix.odin delete mode 100644 core/runtime/core_builtin_soa.odin delete mode 100644 core/runtime/default_allocators_arena.odin delete mode 100644 core/runtime/default_allocators_general.odin delete mode 100644 core/runtime/default_allocators_js.odin delete mode 100644 core/runtime/default_allocators_nil.odin delete mode 100644 core/runtime/default_allocators_wasi.odin delete mode 100644 core/runtime/default_allocators_windows.odin delete mode 100644 core/runtime/default_temporary_allocator.odin delete mode 100644 core/runtime/docs.odin delete mode 100644 core/runtime/dynamic_array_internal.odin delete mode 100644 core/runtime/dynamic_map_internal.odin delete mode 100644 core/runtime/entry_unix.odin delete mode 100644 core/runtime/entry_unix_no_crt_amd64.asm delete mode 100644 core/runtime/entry_unix_no_crt_darwin_arm64.asm delete mode 100644 core/runtime/entry_unix_no_crt_i386.asm delete mode 100644 core/runtime/entry_wasm.odin delete mode 100644 core/runtime/entry_windows.odin delete mode 100644 core/runtime/error_checks.odin delete mode 100644 core/runtime/internal.odin delete mode 100644 core/runtime/os_specific.odin delete mode 100644 core/runtime/os_specific_any.odin delete mode 100644 core/runtime/os_specific_darwin.odin delete mode 100644 core/runtime/os_specific_freestanding.odin delete mode 100644 core/runtime/os_specific_js.odin delete mode 100644 core/runtime/os_specific_wasi.odin delete mode 100644 core/runtime/os_specific_windows.odin delete mode 100644 core/runtime/print.odin delete mode 100644 core/runtime/procs.odin delete mode 100644 core/runtime/procs_darwin.odin delete mode 100644 core/runtime/procs_js.odin delete mode 100644 core/runtime/procs_wasm.odin delete mode 100644 core/runtime/procs_windows_amd64.asm delete mode 100644 core/runtime/procs_windows_amd64.odin delete mode 100644 core/runtime/procs_windows_i386.odin delete mode 100644 core/runtime/udivmod128.odin (limited to 'src/build_settings.cpp') diff --git a/base/runtime/core.odin b/base/runtime/core.odin new file mode 100644 index 000000000..740482493 --- /dev/null +++ b/base/runtime/core.odin @@ -0,0 +1,681 @@ +// 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 +// +// 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 +// +//+no-instrumentation +package runtime + +import "core:intrinsics" + +// NOTE(bill): This must match the compiler's +Calling_Convention :: enum u8 { + Invalid = 0, + Odin = 1, + Contextless = 2, + CDecl = 3, + Std_Call = 4, + Fast_Call = 5, + + None = 6, + Naked = 7, + + _ = 8, // reserved + + Win64 = 9, + SysV = 10, +} + +Type_Info_Enum_Value :: distinct i64 + +Platform_Endianness :: enum u8 { + Platform = 0, + Little = 1, + Big = 2, +} + +// Procedure type to test whether two values of the same type are equal +Equal_Proc :: distinct proc "contextless" (rawptr, rawptr) -> bool +// Procedure type to hash a value, default seed value is 0 +Hasher_Proc :: distinct proc "contextless" (data: rawptr, seed: uintptr = 0) -> uintptr + +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, + pkg: string, + loc: Source_Code_Location, +} +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_Multi_Pointer :: struct { + elem: ^Type_Info, +} +Type_Info_Procedure :: struct { + params: ^Type_Info, // Type_Info_Parameters + results: ^Type_Info, // Type_Info_Parameters + 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, + is_sparse: bool, +} +Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int} +Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int} + +Type_Info_Parameters :: struct { // Only used for procedures parameters and results + types: []^Type_Info, + names: []string, +} +Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually + +Type_Info_Struct :: struct { + types: []^Type_Info, + names: []string, + offsets: []uintptr, + usings: []bool, + tags: []string, + is_packed: bool, + is_raw_union: bool, + is_no_copy: bool, + custom_align: bool, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set + + // 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, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set + + custom_align: bool, + no_nil: bool, + shared_nil: 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, + map_info: ^Map_Info, +} +Type_Info_Bit_Set :: struct { + elem: ^Type_Info, + underlying: ^Type_Info, // Possibly nil + lower: i64, + upper: i64, +} +Type_Info_Simd_Vector :: struct { + elem: ^Type_Info, + elem_size: int, + count: int, +} +Type_Info_Relative_Pointer :: struct { + pointer: ^Type_Info, // ^T + base_integer: ^Type_Info, +} +Type_Info_Relative_Multi_Pointer :: struct { + pointer: ^Type_Info, // [^]T + base_integer: ^Type_Info, +} +Type_Info_Matrix :: struct { + elem: ^Type_Info, + elem_size: int, + elem_stride: int, // elem_stride >= row_count + row_count: int, + column_count: int, + // Total element count = column_count * elem_stride +} +Type_Info_Soa_Pointer :: struct { + elem: ^Type_Info, +} + +Type_Info_Flag :: enum u8 { + Comparable = 0, + Simple_Compare = 1, +} +Type_Info_Flags :: distinct bit_set[Type_Info_Flag; u32] + +Type_Info :: struct { + size: int, + align: int, + flags: Type_Info_Flags, + 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_Multi_Pointer, + Type_Info_Procedure, + Type_Info_Array, + Type_Info_Enumerated_Array, + Type_Info_Dynamic_Array, + Type_Info_Slice, + Type_Info_Parameters, + Type_Info_Struct, + Type_Info_Union, + Type_Info_Enum, + Type_Info_Map, + Type_Info_Bit_Set, + Type_Info_Simd_Vector, + Type_Info_Relative_Pointer, + Type_Info_Relative_Multi_Pointer, + Type_Info_Matrix, + Type_Info_Soa_Pointer, + }, +} + +// NOTE(bill): This must match the compiler's +Typeid_Kind :: enum u8 { + Invalid, + Integer, + Rune, + Float, + Complex, + Quaternion, + String, + Boolean, + Any, + Type_Id, + Pointer, + Multi_Pointer, + Procedure, + Array, + Enumerated_Array, + Dynamic_Array, + Slice, + Tuple, + Struct, + Union, + Enum, + Map, + Bit_Set, + Simd_Vector, + Relative_Pointer, + Relative_Multi_Pointer, + Matrix, + Soa_Pointer, +} +#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 + +when ODIN_OS == .Windows { + // NOTE(Jeroen): If we're a Windows DLL, fwdReason will be populated. + // This tells a DLL if it's first loaded, about to be unloaded, or a thread is joining/exiting. + + DLL_Forward_Reason :: enum u32 { + Process_Detach = 0, // About to unload DLL + Process_Attach = 1, // Entry point + Thread_Attach = 2, + Thread_Detach = 3, + } + dll_forward_reason: DLL_Forward_Reason +} + +// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it) + + +Source_Code_Location :: struct { + file_path: string, + line, column: i32, + procedure: string, +} + +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, + Alloc_Non_Zeroed, + Resize_Non_Zeroed, +} + +Allocator_Mode_Set :: distinct bit_set[Allocator_Mode] + +Allocator_Query_Info :: struct { + pointer: rawptr, + size: Maybe(int), + alignment: Maybe(int), +} + +Allocator_Error :: enum byte { + None = 0, + Out_Of_Memory = 1, + Invalid_Pointer = 2, + Invalid_Argument = 3, + Mode_Not_Implemented = 4, +} + +Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error) +Allocator :: struct { + procedure: Allocator_Proc, + data: rawptr, +} + +Byte :: 1 +Kilobyte :: 1024 * Byte +Megabyte :: 1024 * Kilobyte +Gigabyte :: 1024 * Megabyte +Terabyte :: 1024 * Gigabyte +Petabyte :: 1024 * Terabyte +Exabyte :: 1024 * Petabyte + +// 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, + + user_ptr: rawptr, + user_index: int, + + // Internal use only + _internal: rawptr, +} + + +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, +} + +// The raw, type-erased representation of a map. +// +// 32-bytes on 64-bit +// 16-bytes on 32-bit +Raw_Map :: struct { + // A single allocation spanning all keys, values, and hashes. + // { + // k: Map_Cell(K) * (capacity / ks_per_cell) + // v: Map_Cell(V) * (capacity / vs_per_cell) + // h: Map_Cell(H) * (capacity / hs_per_cell) + // } + // + // The data is allocated assuming 64-byte alignment, meaning the address is + // always a multiple of 64. This means we have 6 bits of zeros in the pointer + // to store the capacity. We can store a value as large as 2^6-1 or 63 in + // there. This conveniently is the maximum log2 capacity we can have for a map + // as Odin uses signed integers to represent capacity. + // + // Since the hashes are backed by Map_Hash, which is just a 64-bit unsigned + // integer, the cell structure for hashes is unnecessary because 64/8 is 8 and + // requires no padding, meaning it can be indexed as a regular array of + // Map_Hash directly, though for consistency sake it's written as if it were + // an array of Map_Cell(Map_Hash). + data: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits + len: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits + allocator: Allocator, // 16-bytes on 64-bits, 8-bytes on 32-bits +} + +Raw_Any :: struct { + data: rawptr, + id: typeid, +} + +Raw_Cstring :: struct { + data: [^]byte, +} + +Raw_Soa_Pointer :: struct { + data: rawptr, + index: int, +} + + + +/* + // Defined internally by the compiler + Odin_OS_Type :: enum int { + Unknown, + Windows, + Darwin, + Linux, + Essence, + FreeBSD, + OpenBSD, + WASI, + JS, + Freestanding, + } +*/ +Odin_OS_Type :: type_of(ODIN_OS) + +/* + // Defined internally by the compiler + Odin_Arch_Type :: enum int { + Unknown, + amd64, + i386, + arm32, + arm64, + wasm32, + wasm64p32, + } +*/ +Odin_Arch_Type :: type_of(ODIN_ARCH) + +/* + // Defined internally by the compiler + Odin_Build_Mode_Type :: enum int { + Executable, + Dynamic, + Object, + Assembly, + LLVM_IR, + } +*/ +Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE) + +/* + // Defined internally by the compiler + Odin_Endian_Type :: enum int { + Unknown, + Little, + Big, + } +*/ +Odin_Endian_Type :: type_of(ODIN_ENDIAN) + + +/* + // Defined internally by the compiler + Odin_Platform_Subtarget_Type :: enum int { + Default, + iOS, + } +*/ +Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) + +/* + // Defined internally by the compiler + Odin_Sanitizer_Flag :: enum u32 { + Address = 0, + Memory = 1, + Thread = 2, + } + Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32] + + ODIN_SANITIZER_FLAGS // is a constant +*/ +Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS) + + +///////////////////////////// +// Init Startup Procedures // +///////////////////////////// + +// IMPORTANT NOTE(bill): Do not call this unless you want to explicitly set up the entry point and how it gets called +// This is probably only useful for freestanding targets +foreign { + @(link_name="__$startup_runtime") + _startup_runtime :: proc "odin" () --- + @(link_name="__$cleanup_runtime") + _cleanup_runtime :: proc "odin" () --- +} + +_cleanup_runtime_contextless :: proc "contextless" () { + context = default_context() + _cleanup_runtime() +} + + +///////////////////////////// +///////////////////////////// +///////////////////////////// + + +type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { + if info == nil { + 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 { + 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: break loop + } + } + return base +} +type_info_base_without_enum :: type_info_core + +__type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check { + MASK :: 1<<(8*size_of(typeid) - 8) - 1 + data := transmute(uintptr)id + n := int(data & MASK) + if n < 0 || n >= len(type_table) { + n = 0 + } + return &type_table[n] +} + +when !ODIN_NO_RTTI { + 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_core(type_info_of(id)) + return ti.id + } + typeid_base_without_enum :: typeid_core +} + + + +debug_trap :: intrinsics.debug_trap +trap :: intrinsics.trap +read_cycle_counter :: intrinsics.read_cycle_counter + + + +default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location) { + // 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 { + return + } + c^ = other^ + __init_context(c) +} + +@private +__init_context :: proc "contextless" (c: ^Context) { + if c == nil { + return + } + + // NOTE(bill): Do not initialize these procedures with a call as they are not defined with the "contextless" calling convention + c.allocator.procedure = default_allocator_proc + c.allocator.data = nil + + c.temp_allocator.procedure = default_temp_allocator_proc + when !NO_DEFAULT_TEMP_ALLOCATOR { + c.temp_allocator.data = &global_default_temp_allocator_data + } + + when !ODIN_DISABLE_ASSERT { + c.assertion_failure_proc = default_assertion_failure_proc + } + + c.logger.procedure = default_logger_proc + c.logger.data = nil +} + +default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! { + when ODIN_OS == .Freestanding { + // Do nothing + } else { + when !ODIN_DISABLE_ASSERT { + print_caller_location(loc) + print_string(" ") + } + print_string(prefix) + if len(message) > 0 { + print_string(": ") + print_string(message) + } + print_byte('\n') + } + trap() +} diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin new file mode 100644 index 000000000..3f4ebbc74 --- /dev/null +++ b/base/runtime/core_builtin.odin @@ -0,0 +1,915 @@ +package runtime + +import "core:intrinsics" + +@builtin +Maybe :: union($T: typeid) {T} + + +@(builtin, require_results) +container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T + where intrinsics.type_has_field(T, field_name), + intrinsics.type_field_type(T, field_name) == Field_Type { + offset :: offset_of_by_string(T, field_name) + return (^T)(uintptr(ptr) - offset) if ptr != nil else nil +} + + +when !NO_DEFAULT_TEMP_ALLOCATOR { + @thread_local global_default_temp_allocator_data: Default_Temp_Allocator +} + +@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR) +init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) { + when !NO_DEFAULT_TEMP_ALLOCATOR { + default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator) + } +} + + +// `copy_slice` is a built-in procedure that copies elements from a source slice `src` to a destination slice `dst`. +// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum +// of len(src) and len(dst). +// +// Prefer the procedure group `copy`. +@builtin +copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { + n := max(0, min(len(dst), len(src))) + if n > 0 { + intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E)) + } + return n +} +// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`. +// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum +// of len(src) and len(dst). +// +// Prefer the procedure group `copy`. +@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 { + intrinsics.mem_copy(raw_data(dst), raw_data(src), n) + } + return n +} +// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`. +// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum +// of len(src) and len(dst). +@builtin +copy :: proc{copy_slice, copy_from_string} + + + +// `unordered_remove` removed the element at the specified `index`. It does so by replacing the current end value +// with the old value, and reducing the length of the dynamic array by 1. +// +// Note: This is an O(1) operation. +// Note: If you the elements to remain in their order, use `ordered_remove`. +// Note: If the index is out of bounds, this procedure will panic. +@builtin +unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { + bounds_check_error_loc(loc, index, len(array)) + n := len(array)-1 + if index != n { + array[index] = array[n] + } + (^Raw_Dynamic_Array)(array).len -= 1 +} +// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`. +// Note: If the index is out of bounds, this procedure will panic. +@builtin +ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { + bounds_check_error_loc(loc, index, len(array)) + if index+1 < len(array) { + copy(array[index:], array[index+1:]) + } + (^Raw_Dynamic_Array)(array).len -= 1 +} + +// `remove_range` removes a range of elements specified by the range `lo` and `hi`, whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If the range is out of bounds, this procedure will panic. +@builtin +remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_location) #no_bounds_check { + slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) + n := max(hi-lo, 0) + if n > 0 { + if hi != len(array) { + copy(array[lo:], array[hi:]) + } + (^Raw_Dynamic_Array)(array).len -= n + } +} + + +// `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[len(array)-1] + (^Raw_Dynamic_Array)(array).len -= 1 + return res +} + + +// `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[len(array)-1], true + (^Raw_Dynamic_Array)(array).len -= 1 + return +} + +// `pop_front` will remove and return the first value of dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[0] + if len(array) > 1 { + copy(array[0:], array[1:]) + } + (^Raw_Dynamic_Array)(array).len -= 1 + return res +} + +// `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[0], true + if len(array) > 1 { + copy(array[0:], array[1:]) + } + (^Raw_Dynamic_Array)(array).len -= 1 + return +} + + +// `clear` will set the length of a passed dynamic array or map to `0` +@builtin +clear :: proc{clear_dynamic_array, clear_map} + +// `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). +@builtin +reserve :: proc{reserve_dynamic_array, reserve_map} + +@builtin +non_zero_reserve :: proc{non_zero_reserve_dynamic_array} + +// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`). +@builtin +resize :: proc{resize_dynamic_array} + +@builtin +non_zero_resize :: proc{non_zero_resize_dynamic_array} + +// Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity. +@builtin +shrink :: proc{shrink_dynamic_array, shrink_map} + +// `free` will try to free the passed pointer, with the given `allocator` if the allocator supports this operation. +@builtin +free :: proc{mem_free} + +// `free_all` will try to free/reset all of the memory of the given `allocator` if the allocator supports this operation. +@builtin +free_all :: proc{mem_free_all} + + + +// `delete_string` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free_with_size(raw_data(str), len(str), allocator, loc) +} +// `delete_cstring` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free((^byte)(str), allocator, loc) +} +// `delete_dynamic_array` will try to free the underlying data of the passed dynamic array, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error { + return mem_free_with_size(raw_data(array), cap(array)*size_of(E), array.allocator, loc) +} +// `delete_slice` will try to free the underlying data of the passed sliced, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc) +} +// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error { + return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc) +} + + +// `delete` will try to free the underlying data of the passed built-in data structure (string, cstring, dynamic array, slice, or map), with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer `delete` over the specific `delete_*` procedures where possible. +@builtin +delete :: proc{ + delete_string, + delete_cstring, + delete_dynamic_array, + delete_slice, + delete_map, + delete_soa_slice, + delete_soa_dynamic_array, +} + + +// The new built-in procedure allocates memory. The first argument is a type, not a value, and the value +// return is a pointer to a newly allocated value of that type using the specified allocator, default is context.allocator +@(builtin, require_results) +new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) #optional_allocator_error { + return new_aligned(T, align_of(T), allocator, loc) +} +@(require_results) +new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { + data := mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return + t = (^T)(raw_data(data)) + return +} + +@(builtin, require_results) +new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error { + t_data := mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return + t = (^T)(raw_data(t_data)) + if t != nil { + t^ = data + } + return +} + +DEFAULT_RESERVE_CAPACITY :: 16 + +@(require_results) +make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + make_slice_error_loc(loc, len) + data, err := mem_alloc_bytes(size_of(E)*len, alignment, allocator, loc) + if data == nil && size_of(E) != 0 { + return nil, err + } + s := Raw_Slice{raw_data(data), len} + return transmute(T)s, err +} + +// `make_slice` allocates and initializes a slice. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + return make_aligned(T, len, align_of(E), allocator, loc) +} +// `make_dynamic_array` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc) +} +// `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + return make_dynamic_array_len_cap(T, len, len, allocator, loc) +} +// `make_dynamic_array_len_cap` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + make_dynamic_array_error_loc(loc, len, cap) + data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return + s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator} + if data == nil && size_of(E) != 0 { + s.len, s.cap = 0, 0 + } + array = transmute(T)s + return +} +// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { + make_map_expr_error_loc(loc, capacity) + context.allocator = allocator + + err = reserve_map(&m, capacity, loc) + return +} +// `make_multi_pointer` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// This is "similar" to doing `raw_data(make([]E, len, allocator))`. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error { + make_slice_error_loc(loc, len) + data := mem_alloc_bytes(size_of(E)*len, align_of(E), allocator, loc) or_return + if data == nil && size_of(E) != 0 { + return + } + mp = cast(T)raw_data(data) + return +} + + +// `make` built-in procedure allocates and initializes a value of type slice, dynamic array, map, or multi-pointer (only). +// +// Similar to `new`, the first argument is a type, not a value. Unlike new, make's return type is the same as the +// type of its argument, not a pointer to it. +// Make uses the specified allocator, default is context.allocator. +@builtin +make :: proc{ + make_slice, + make_dynamic_array, + make_dynamic_array_len, + make_dynamic_array_len_cap, + make_map, + make_multi_pointer, +} + + + +// `clear_map` will set the length of a passed map to `0` +// +// Note: Prefer the procedure group `clear` +@builtin +clear_map :: proc "contextless" (m: ^$T/map[$K]$V) { + if m == nil { + return + } + map_clear_dynamic((^Raw_Map)(m), map_info(T)) +} + +// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`). +// +// Note: Prefer the procedure group `reserve` +@builtin +reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error { + return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil +} + +// Shrinks the capacity of a map down to the current length. +// +// Note: Prefer the procedure group `shrink` +@builtin +shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + if m != nil { + return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc) + } + return +} + +// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map. +// If m is nil, or there is no such element, this procedure is a no-op +@builtin +delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) { + if m != nil { + key := key + old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key)) + if ok { + deleted_key = (^K)(old_k)^ + deleted_value = (^V)(old_v)^ + } + } + return +} + +_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + when size_of(E) == 0 { + array := (^Raw_Dynamic_Array)(array) + array.len += 1 + return 1, nil + } else { + if cap(array) < len(array)+1 { + cap := 2 * cap(array) + max(8, 1) + + // do not 'or_return' here as it could be a partial success + if should_zero { + err = reserve(array, cap, loc) + } else { + err = non_zero_reserve(array, cap, loc) + } + } + if cap(array)-len(array) > 0 { + a := (^Raw_Dynamic_Array)(array) + when size_of(E) != 0 { + data := ([^]E)(a.data) + assert(data != nil, loc=loc) + data[a.len] = arg + } + a.len += 1 + return 1, err + } + return 0, err + } +} + +@builtin +append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem(array, arg, true, loc=loc) +} + +@builtin +non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem(array, arg, false, loc=loc) +} + +_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + + arg_len := len(args) + if arg_len <= 0 { + return 0, nil + } + + when size_of(E) == 0 { + array := (^Raw_Dynamic_Array)(array) + array.len += arg_len + return arg_len, nil + } else { + if cap(array) < len(array)+arg_len { + cap := 2 * cap(array) + max(8, arg_len) + + // do not 'or_return' here as it could be a partial success + if should_zero { + err = reserve(array, cap, loc) + } else { + err = non_zero_reserve(array, cap, loc) + } + } + arg_len = min(cap(array)-len(array), arg_len) + if arg_len > 0 { + a := (^Raw_Dynamic_Array)(array) + when size_of(E) != 0 { + data := ([^]E)(a.data) + assert(data != nil, loc=loc) + intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) + } + a.len += arg_len + } + return arg_len, err + } +} + +@builtin +append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elems(array, true, loc, ..args) +} + +@builtin +non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elems(array, false, loc, ..args) +} + +// The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type +_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + args := transmute([]E)arg + if should_zero { + return append_elems(array, ..args, loc=loc) + } else { + return non_zero_append_elems(array, ..args, loc=loc) + } +} + +@builtin +append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem_string(array, arg, true, loc) +} +@builtin +non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem_string(array, arg, false, loc) +} + + +// The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type +@builtin +append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + n_arg: int + for arg in args { + n_arg, err = append(array, ..transmute([]E)(arg), loc=loc) + n += n_arg + if err != nil { + return + } + } + return +} + +// The append built-in procedure appends elements to the end of a dynamic array +@builtin append :: proc{append_elem, append_elems, append_elem_string} +@builtin non_zero_append :: proc{non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string} + + +@builtin +append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + prev_len := len(array) + resize(array, len(array)+1, loc) or_return + return len(array)-prev_len, nil +} + + +@builtin +inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if array == nil { + return + } + n := max(len(array), index) + m :: 1 + new_size := n + m + + resize(array, new_size, loc) or_return + when size_of(E) != 0 { + copy(array[index + m:], array[index:]) + array[index] = arg + } + ok = true + return +} + +@builtin +inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if array == nil { + return + } + if len(args) == 0 { + ok = true + return + } + + n := max(len(array), index) + m := len(args) + new_size := n + m + + resize(array, new_size, loc) or_return + when size_of(E) != 0 { + copy(array[index + m:], array[index:]) + copy(array[index:], args) + } + ok = true + return +} + +@builtin +inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if array == nil { + return + } + if len(arg) == 0 { + ok = true + return + } + + n := max(len(array), index) + m := len(arg) + new_size := n + m + + resize(array, new_size, loc) or_return + copy(array[index+m:], array[index:]) + copy(array[index:], arg) + ok = true + return +} + +@builtin inject_at :: proc{inject_at_elem, inject_at_elems, inject_at_elem_string} + + + +@builtin +assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if index < len(array) { + array[index] = arg + ok = true + } else { + resize(array, index+1, loc) or_return + array[index] = arg + ok = true + } + return +} + + +@builtin +assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + new_size := index + len(args) + if len(args) == 0 { + ok = true + } else if new_size < len(array) { + copy(array[index:], args) + ok = true + } else { + resize(array, new_size, loc) or_return + copy(array[index:], args) + ok = true + } + return +} + + +@builtin +assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + new_size := index + len(arg) + if len(arg) == 0 { + ok = true + } else if new_size < len(array) { + copy(array[index:], arg) + ok = true + } else { + resize(array, new_size, loc) or_return + copy(array[index:], arg) + ok = true + } + return +} + +@builtin assign_at :: proc{assign_at_elem, assign_at_elems, assign_at_elem_string} + + + + +// `clear_dynamic_array` will set the length of a passed dynamic array to `0` +// +// Note: Prefer the procedure group `clear`. +@builtin +clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { + if array != nil { + (^Raw_Dynamic_Array)(array).len = 0 + } +} + +// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). +// +// Note: Prefer the procedure group `reserve`. +_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + a := (^Raw_Dynamic_Array)(array) + + if capacity <= a.cap { + return nil + } + + 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: []byte + if should_zero { + new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } else { + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } + if new_data == nil && new_size > 0 { + return .Out_Of_Memory + } + + a.data = raw_data(new_data) + a.cap = capacity + return nil +} + +@builtin +reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { + return _reserve_dynamic_array(array, capacity, true, loc) +} + +@builtin +non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { + return _reserve_dynamic_array(array, capacity, false, loc) +} + +// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). +// +// Note: Prefer the procedure group `resize` +_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + a := (^Raw_Dynamic_Array)(array) + + if length <= a.cap { + a.len = max(length, 0) + return nil + } + + 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 : []byte + if should_zero { + new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } else { + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } + if new_data == nil && new_size > 0 { + return .Out_Of_Memory + } + + a.data = raw_data(new_data) + a.len = length + a.cap = length + return nil +} + +@builtin +resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array(array, length, true, loc=loc) +} + +@builtin +non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array(array, length, false, loc=loc) +} + +/* + Shrinks the capacity of a dynamic array down to the current length, or the given capacity. + + If `new_cap` is negative, then `len(array)` is used. + + Returns false if `cap(array) < new_cap`, or the allocator report failure. + + If `len(array) < new_cap`, then `len(array)` will be left unchanged. + + Note: Prefer the procedure group `shrink` +*/ +shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + if array == nil { + return + } + a := (^Raw_Dynamic_Array)(array) + + new_cap := new_cap if new_cap >= 0 else a.len + + if new_cap > a.cap { + return + } + + if a.allocator.procedure == nil { + a.allocator = context.allocator + } + assert(a.allocator.procedure != nil) + + old_size := a.cap * size_of(E) + new_size := new_cap * size_of(E) + + new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return + + a.data = raw_data(new_data) + a.len = min(new_cap, a.len) + a.cap = new_cap + return true, nil +} + +@builtin +map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) { + key, value := key, value + return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)) +} + + +@builtin +incl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { + s^ |= {elem} +} +@builtin +incl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { + for elem in elems { + s^ |= {elem} + } +} +@builtin +incl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { + s^ |= other +} +@builtin +excl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { + s^ &~= {elem} +} +@builtin +excl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { + for elem in elems { + s^ &~= {elem} + } +} +@builtin +excl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { + s^ &~= other +} + +@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 { + return int(intrinsics.count_ones(transmute(u8)s)) + } else when size_of(S) == 2 { + return int(intrinsics.count_ones(transmute(u16)s)) + } else when size_of(S) == 4 { + return int(intrinsics.count_ones(transmute(u32)s)) + } else when size_of(S) == 8 { + return int(intrinsics.count_ones(transmute(u64)s)) + } else when size_of(S) == 16 { + return int(intrinsics.count_ones(transmute(u128)s)) + } else { + #panic("Unhandled card bit_set size") + } +} + + + +@builtin +@(disabled=ODIN_DISABLE_ASSERT) +assert :: proc(condition: bool, message := "", loc := #caller_location) { + if !condition { + // NOTE(bill): This is wrapped in a procedure call + // to improve performance to make the CPU not + // execute speculatively, making it about an order of + // magnitude faster + @(cold) + internal :: 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) + } + internal(message, loc) + } +} + +@builtin +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 +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) +} diff --git a/base/runtime/core_builtin_matrix.odin b/base/runtime/core_builtin_matrix.odin new file mode 100644 index 000000000..7d60d625c --- /dev/null +++ b/base/runtime/core_builtin_matrix.odin @@ -0,0 +1,274 @@ +package runtime + +import "core:intrinsics" +_ :: intrinsics + + +@(builtin) +determinant :: proc{ + matrix1x1_determinant, + matrix2x2_determinant, + matrix3x3_determinant, + matrix4x4_determinant, +} + +@(builtin) +adjugate :: proc{ + matrix1x1_adjugate, + matrix2x2_adjugate, + matrix3x3_adjugate, + matrix4x4_adjugate, +} + +@(builtin) +inverse_transpose :: proc{ + matrix1x1_inverse_transpose, + matrix2x2_inverse_transpose, + matrix3x3_inverse_transpose, + matrix4x4_inverse_transpose, +} + + +@(builtin) +inverse :: proc{ + matrix1x1_inverse, + matrix2x2_inverse, + matrix3x3_inverse, + matrix4x4_inverse, +} + +@(builtin, require_results) +hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 { + return conj(transpose(m)) +} + +@(builtin, require_results) +matrix_trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) { + for i in 0.. (minor: T) where N > 1 { + K :: N-1 + cut_down: matrix[K, K]T + for col_idx in 0..= column) + for row_idx in 0..= row) + cut_down[row_idx, col_idx] = m[i, j] + } + } + return determinant(cut_down) +} + + + +@(builtin, require_results) +matrix1x1_determinant :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) { + return m[0, 0] +} + +@(builtin, require_results) +matrix2x2_determinant :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) { + return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] +} +@(builtin, require_results) +matrix3x3_determinant :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) { + a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) + b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) + c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) + return a + b + c +} +@(builtin, require_results) +matrix4x4_determinant :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) { + a := adjugate(m) + #no_bounds_check for i in 0..<4 { + det += m[0, i] * a[0, i] + } + return +} + + + + +@(builtin, require_results) +matrix1x1_adjugate :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y = x + return +} + +@(builtin, require_results) +matrix2x2_adjugate :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[1, 0] + y[1, 0] = -x[0, 1] + y[1, 1] = +x[0, 0] + return +} + +@(builtin, require_results) +matrix3x3_adjugate :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + + +@(builtin, require_results) +matrix4x4_adjugate :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, i, j) + } + } + return +} + +@(builtin, require_results) +matrix1x1_inverse_transpose :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(builtin, require_results) +matrix2x2_inverse_transpose :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[1, 0] = -x[0, 1] / d + y[0, 1] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[1, 0] = -x[0, 1] * id + y[0, 1] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(builtin, require_results) +matrix3x3_inverse_transpose :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[i, j] * id + } + } + } + return +} + +@(builtin, require_results) +matrix4x4_inverse_transpose :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d: T + for i in 0..<4 { + d += x[0, i] * a[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[i, j] * id + } + } + } + return +} + +@(builtin, require_results) +matrix1x1_inverse :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(builtin, require_results) +matrix2x2_inverse :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[0, 1] = -x[0, 1] / d + y[1, 0] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[0, 1] = -x[0, 1] * id + y[1, 0] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(builtin, require_results) +matrix3x3_inverse :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[j, i] * id + } + } + } + return +} + +@(builtin, require_results) +matrix4x4_inverse :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d: T + for i in 0..<4 { + d += x[0, i] * a[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[j, i] * id + } + } + } + return +} diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin new file mode 100644 index 000000000..6313a28f5 --- /dev/null +++ b/base/runtime/core_builtin_soa.odin @@ -0,0 +1,428 @@ +package runtime + +import "core:intrinsics" +_ :: intrinsics + +/* + + SOA types are implemented with this sort of layout: + + SOA Fixed Array + struct { + f0: [N]T0, + f1: [N]T1, + f2: [N]T2, + } + + SOA Slice + struct { + f0: ^T0, + f1: ^T1, + f2: ^T2, + + len: int, + } + + SOA Dynamic Array + struct { + f0: ^T0, + f1: ^T1, + f2: ^T2, + + len: int, + cap: int, + allocator: Allocator, + } + + A footer is used rather than a header purely to simplify access to the fields internally + i.e. field index of the AOS == SOA + +*/ + + +Raw_SOA_Footer_Slice :: struct { + len: int, +} + +Raw_SOA_Footer_Dynamic_Array :: struct { + len: int, + cap: int, + allocator: Allocator, +} + +@(builtin, require_results) +raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { + if array == nil { + return nil + } + field_count := uintptr(intrinsics.type_struct_field_count(E)) + footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr)) + return +} +@(builtin, require_results) +raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { + if array == nil { + return nil + } + field_count: uintptr + when intrinsics.type_is_array(E) { + field_count = len(E) + } else { + field_count = uintptr(intrinsics.type_struct_field_count(E)) + } + footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr)) + return +} +raw_soa_footer :: proc{ + raw_soa_footer_slice, + raw_soa_footer_dynamic_array, +} + + + +@(builtin, require_results) +make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + if length <= 0 { + return + } + + footer := raw_soa_footer(&array) + if size_of(E) == 0 { + footer.len = length + return + } + + max_align := max(alignment, align_of(E)) + + ti := type_info_of(typeid_of(T)) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + + field_count := uintptr(intrinsics.type_struct_field_count(E)) + + total_size := 0 + for i in 0.. (array: T, err: Allocator_Error) #optional_allocator_error { + return make_soa_aligned(T, length, align_of(E), allocator, loc) +} + +@(builtin, require_results) +make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + context.allocator = allocator + reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return + return array, nil +} + +@(builtin, require_results) +make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + context.allocator = allocator + resize_soa(&array, length, loc) or_return + return array, nil +} + +@(builtin, require_results) +make_soa_dynamic_array_len_cap :: proc($T: typeid/#soa[dynamic]$E, #any_int length, capacity: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + context.allocator = allocator + reserve_soa(&array, capacity, loc) or_return + resize_soa(&array, length, loc) or_return + return array, nil +} + + +@builtin +make_soa :: proc{ + make_soa_slice, + make_soa_dynamic_array, + make_soa_dynamic_array_len, + make_soa_dynamic_array_len_cap, +} + + +@builtin +resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + reserve_soa(array, length, loc) or_return + footer := raw_soa_footer(array) + footer.len = length + return nil +} + +@builtin +reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + + old_cap := cap(array) + if capacity <= old_cap { + return nil + } + + if array.allocator.procedure == nil { + array.allocator = context.allocator + } + assert(array.allocator.procedure != nil) + + footer := raw_soa_footer(array) + if size_of(E) == 0 { + footer.cap = capacity + return nil + } + + ti := type_info_of(typeid_of(T)) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + + field_count: uintptr + when intrinsics.type_is_array(E) { + field_count = len(E) + } else { + field_count = uintptr(intrinsics.type_struct_field_count(E)) + } + assert(footer.cap == old_cap) + + old_size := 0 + new_size := 0 + + max_align :: align_of(E) + for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + + if cap(array) <= len(array) + 1 { + cap := 2 * cap(array) + 8 + err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success + } + + footer := raw_soa_footer(array) + + if size_of(E) > 0 && cap(array)-len(array) > 0 { + ti := type_info_of(T) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + field_count: uintptr + when intrinsics.type_is_array(E) { + field_count = len(E) + } else { + field_count = uintptr(intrinsics.type_struct_field_count(E)) + } + + data := (^rawptr)(array)^ + + soa_offset := 0 + item_offset := 0 + + arg_copy := arg + arg_ptr := &arg_copy + + max_align :: align_of(E) + for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return + } + + arg_len := len(args) + if arg_len == 0 { + return + } + + if cap(array) <= len(array)+arg_len { + cap := 2 * cap(array) + max(8, arg_len) + err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success + } + arg_len = min(cap(array)-len(array), arg_len) + + footer := raw_soa_footer(array) + if size_of(E) > 0 && arg_len > 0 { + ti := type_info_of(typeid_of(T)) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + field_count := uintptr(intrinsics.type_struct_field_count(E)) + + data := (^rawptr)(array)^ + + soa_offset := 0 + item_offset := 0 + + args_ptr := &args[0] + + max_align :: align_of(E) + for i in 0.. Allocator_Error { + when intrinsics.type_struct_field_count(E) != 0 { + array := array + ptr := (^rawptr)(&array)^ + free(ptr, allocator, loc) or_return + } + return nil +} + +delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error { + when intrinsics.type_struct_field_count(E) != 0 { + array := array + ptr := (^rawptr)(&array)^ + footer := raw_soa_footer(&array) + free(ptr, footer.allocator, loc) or_return + } + return nil +} + + +@builtin +delete_soa :: proc{ + delete_soa_slice, + delete_soa_dynamic_array, +} + + +clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) { + when intrinsics.type_struct_field_count(E) != 0 { + footer := raw_soa_footer(array) + footer.len = 0 + } +} + +@builtin +clear_soa :: proc{ + clear_soa_dynamic_array, +} \ No newline at end of file diff --git a/base/runtime/default_allocators_arena.odin b/base/runtime/default_allocators_arena.odin new file mode 100644 index 000000000..1fe3c6cfc --- /dev/null +++ b/base/runtime/default_allocators_arena.odin @@ -0,0 +1,304 @@ +package runtime + +import "core:intrinsics" + +DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) + +Memory_Block :: struct { + prev: ^Memory_Block, + allocator: Allocator, + base: [^]byte, + used: uint, + capacity: uint, +} + +Arena :: struct { + backing_allocator: Allocator, + curr_block: ^Memory_Block, + total_used: uint, + total_capacity: uint, + minimum_block_size: uint, + temp_count: uint, +} + +@(private, require_results) +safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) { + z, did_overflow := intrinsics.overflow_add(x, y) + return z, !did_overflow +} + +@(require_results) +memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) { + total_size := uint(capacity + max(alignment, size_of(Memory_Block))) + base_offset := uintptr(max(alignment, size_of(Memory_Block))) + + min_alignment: int = max(16, align_of(Memory_Block), int(alignment)) + data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return + block = (^Memory_Block)(raw_data(data)) + end := uintptr(raw_data(data)[len(data):]) + + block.allocator = allocator + block.base = ([^]byte)(uintptr(block) + base_offset) + block.capacity = uint(end - uintptr(block.base)) + + // Should be zeroed + assert(block.used == 0) + assert(block.prev == nil) + return +} + +memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { + if block_to_free != nil { + allocator := block_to_free.allocator + mem_free(block_to_free, allocator, loc) + } +} + +@(require_results) +alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) { + calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { + alignment_offset := uint(0) + ptr := uintptr(block.base[block.used:]) + mask := alignment-1 + if ptr & mask != 0 { + alignment_offset = uint(alignment - (ptr & mask)) + } + return alignment_offset + + } + if block == nil { + return nil, .Out_Of_Memory + } + alignment_offset := calc_alignment_offset(block, uintptr(alignment)) + size, size_ok := safe_add(min_size, alignment_offset) + if !size_ok { + err = .Out_Of_Memory + return + } + + if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity { + err = .Out_Of_Memory + return + } + data = block.base[block.used+alignment_offset:][:min_size] + block.used += size + return +} + +@(require_results) +arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint { + p := ptr + modulo := p & (align-1) + if modulo != 0 { + p += align - modulo + } + return p + } + + assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) + + size := size + if size == 0 { + return + } + + needed := align_forward_uint(size, alignment) + if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.capacity { + if arena.minimum_block_size == 0 { + arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE + } + + block_size := max(needed, arena.minimum_block_size) + + if arena.backing_allocator.procedure == nil { + arena.backing_allocator = default_allocator() + } + + new_block := memory_block_alloc(arena.backing_allocator, block_size, alignment, loc) or_return + new_block.prev = arena.curr_block + arena.curr_block = new_block + arena.total_capacity += new_block.capacity + } + + prev_used := arena.curr_block.used + data, err = alloc_from_memory_block(arena.curr_block, size, alignment) + arena.total_used += arena.curr_block.used - prev_used + return +} + +// `arena_init` will initialize the arena with a usuable block. +// This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary +@(require_results) +arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error { + arena^ = {} + arena.backing_allocator = backing_allocator + arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB + new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, 0, loc) or_return + arena.curr_block = new_block + arena.total_capacity += new_block.capacity + return nil +} + + +arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { + if free_block := arena.curr_block; free_block != nil { + arena.curr_block = free_block.prev + + arena.total_capacity -= free_block.capacity + memory_block_dealloc(free_block, loc) + } +} + +// `arena_free_all` will free all but the first memory block, and then reset the memory block +arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { + for arena.curr_block != nil && arena.curr_block.prev != nil { + arena_free_last_memory_block(arena, loc) + } + + if arena.curr_block != nil { + intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) + arena.curr_block.used = 0 + } + arena.total_used = 0 +} + +arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { + for arena.curr_block != nil { + free_block := arena.curr_block + arena.curr_block = free_block.prev + + arena.total_capacity -= free_block.capacity + memory_block_dealloc(free_block, loc) + } + arena.total_used = 0 + arena.total_capacity = 0 +} + +arena_allocator :: proc(arena: ^Arena) -> Allocator { + return Allocator{arena_allocator_proc, arena} +} + +arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location := #caller_location) -> (data: []byte, err: Allocator_Error) { + arena := (^Arena)(allocator_data) + + size, alignment := uint(size), uint(alignment) + old_size := uint(old_size) + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + return arena_alloc(arena, size, alignment, location) + case .Free: + err = .Mode_Not_Implemented + case .Free_All: + arena_free_all(arena, location) + case .Resize, .Resize_Non_Zeroed: + old_data := ([^]byte)(old_memory) + + switch { + case old_data == nil: + return arena_alloc(arena, size, alignment, location) + case size == old_size: + // return old memory + data = old_data[:size] + return + case size == 0: + err = .Mode_Not_Implemented + return + case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: + // shrink data in-place + data = old_data[:size] + return + } + + new_memory := arena_alloc(arena, size, alignment, location) or_return + if new_memory == nil { + return + } + copy(new_memory, old_data[:old_size]) + return new_memory, nil + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features} + } + case .Query_Info: + err = .Mode_Not_Implemented + } + + return +} + + + + +Arena_Temp :: struct { + arena: ^Arena, + block: ^Memory_Block, + used: uint, +} + +@(require_results) +arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { + assert(arena != nil, "nil arena", loc) + + temp.arena = arena + temp.block = arena.curr_block + if arena.curr_block != nil { + temp.used = arena.curr_block.used + } + arena.temp_count += 1 + return +} + +arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { + if temp.arena == nil { + assert(temp.block == nil) + assert(temp.used == 0) + return + } + arena := temp.arena + + if temp.block != nil { + memory_block_found := false + for block := arena.curr_block; block != nil; block = block.prev { + if block == temp.block { + memory_block_found = true + break + } + } + if !memory_block_found { + assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc) + } + + for arena.curr_block != temp.block { + arena_free_last_memory_block(arena) + } + + if block := arena.curr_block; block != nil { + assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) + amount_to_zero := min(block.used-temp.used, block.capacity-block.used) + intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) + block.used = temp.used + } + } + + assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) + arena.temp_count -= 1 +} + +// Ignore the use of a `arena_temp_begin` entirely +arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { + assert(temp.arena != nil, "nil arena", loc) + arena := temp.arena + + assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) + arena.temp_count -= 1 +} + +arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { + assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) +} diff --git a/base/runtime/default_allocators_general.odin b/base/runtime/default_allocators_general.odin new file mode 100644 index 000000000..994a672b0 --- /dev/null +++ b/base/runtime/default_allocators_general.odin @@ -0,0 +1,23 @@ +//+build !windows +//+build !freestanding +//+build !wasi +//+build !js +package runtime + +// TODO(bill): reimplement these procedures in the os_specific stuff +import "core:os" + +when ODIN_DEFAULT_TO_NIL_ALLOCATOR { + _ :: os + + // mem.nil_allocator reimplementation + default_allocator_proc :: nil_allocator_proc + default_allocator :: nil_allocator +} else { + + default_allocator_proc :: os.heap_allocator_proc + + default_allocator :: proc() -> Allocator { + return os.heap_allocator() + } +} diff --git a/base/runtime/default_allocators_js.odin b/base/runtime/default_allocators_js.odin new file mode 100644 index 000000000..715073f08 --- /dev/null +++ b/base/runtime/default_allocators_js.odin @@ -0,0 +1,5 @@ +//+build js +package runtime + +default_allocator_proc :: panic_allocator_proc +default_allocator :: panic_allocator diff --git a/base/runtime/default_allocators_nil.odin b/base/runtime/default_allocators_nil.odin new file mode 100644 index 000000000..c882f5196 --- /dev/null +++ b/base/runtime/default_allocators_nil.odin @@ -0,0 +1,88 @@ +package runtime + +nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + return nil, .Out_Of_Memory + case .Free: + return nil, .None + case .Free_All: + return nil, .Mode_Not_Implemented + case .Resize, .Resize_Non_Zeroed: + if size == 0 { + return nil, .None + } + return nil, .Out_Of_Memory + case .Query_Features: + return nil, .Mode_Not_Implemented + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, .None +} + +nil_allocator :: proc() -> Allocator { + return Allocator{ + procedure = nil_allocator_proc, + data = nil, + } +} + + + +when ODIN_OS == .Freestanding { + default_allocator_proc :: nil_allocator_proc + default_allocator :: nil_allocator +} + + + +panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + switch mode { + case .Alloc: + if size > 0 { + panic("panic allocator, .Alloc called", loc=loc) + } + case .Alloc_Non_Zeroed: + if size > 0 { + panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) + } + case .Resize: + if size > 0 { + panic("panic allocator, .Resize called", loc=loc) + } + case .Resize_Non_Zeroed: + if size > 0 { + panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) + } + case .Free: + if old_memory != nil { + panic("panic allocator, .Free called", loc=loc) + } + case .Free_All: + panic("panic allocator, .Free_All called", loc=loc) + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Query_Features} + } + return nil, nil + + case .Query_Info: + panic("panic allocator, .Query_Info called", loc=loc) + } + + return nil, nil +} + +panic_allocator :: proc() -> Allocator { + return Allocator{ + procedure = panic_allocator_proc, + data = nil, + } +} diff --git a/base/runtime/default_allocators_wasi.odin b/base/runtime/default_allocators_wasi.odin new file mode 100644 index 000000000..a7e6842a6 --- /dev/null +++ b/base/runtime/default_allocators_wasi.odin @@ -0,0 +1,5 @@ +//+build wasi +package runtime + +default_allocator_proc :: panic_allocator_proc +default_allocator :: panic_allocator diff --git a/base/runtime/default_allocators_windows.odin b/base/runtime/default_allocators_windows.odin new file mode 100644 index 000000000..1b0f78428 --- /dev/null +++ b/base/runtime/default_allocators_windows.odin @@ -0,0 +1,44 @@ +//+build windows +package runtime + +when ODIN_DEFAULT_TO_NIL_ALLOCATOR { + // mem.nil_allocator reimplementation + default_allocator_proc :: nil_allocator_proc + default_allocator :: nil_allocator +} else { + default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + data, err = _windows_default_alloc(size, alignment, mode == .Alloc) + + case .Free: + _windows_default_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize, .Resize_Non_Zeroed: + data, err = _windows_default_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Query_Features} + } + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return + } + + default_allocator :: proc() -> Allocator { + return Allocator{ + procedure = default_allocator_proc, + data = nil, + } + } +} diff --git a/base/runtime/default_temporary_allocator.odin b/base/runtime/default_temporary_allocator.odin new file mode 100644 index 000000000..c90f0388d --- /dev/null +++ b/base/runtime/default_temporary_allocator.odin @@ -0,0 +1,79 @@ +package runtime + +DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte) +NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR + +when NO_DEFAULT_TEMP_ALLOCATOR { + Default_Temp_Allocator :: struct {} + + default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {} + + default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {} + + default_temp_allocator_proc :: nil_allocator_proc + + @(require_results) + default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { + return + } + + default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { + } +} else { + Default_Temp_Allocator :: struct { + arena: Arena, + } + + default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) { + _ = arena_init(&s.arena, uint(size), backing_allocator) + } + + default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) { + if s != nil { + arena_destroy(&s.arena) + s^ = {} + } + } + + default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + + s := (^Default_Temp_Allocator)(allocator_data) + return arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc) + } + + @(require_results) + default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { + if context.temp_allocator.data == &global_default_temp_allocator_data { + temp = arena_temp_begin(&global_default_temp_allocator_data.arena, loc) + } + return + } + + default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { + arena_temp_end(temp, loc) + } + + @(fini, private) + _destroy_temp_allocator_fini :: proc() { + default_temp_allocator_destroy(&global_default_temp_allocator_data) + } +} + +@(deferred_out=default_temp_allocator_temp_end) +DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (Arena_Temp, Source_Code_Location) { + if ignore { + return {}, loc + } else { + return default_temp_allocator_temp_begin(loc), loc + } +} + + +default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator { + return Allocator{ + procedure = default_temp_allocator_proc, + data = allocator, + } +} diff --git a/base/runtime/docs.odin b/base/runtime/docs.odin new file mode 100644 index 000000000..a520584c5 --- /dev/null +++ b/base/runtime/docs.odin @@ -0,0 +1,179 @@ +package runtime + +/* + +package runtime has numerous entities (declarations) which are required by the compiler to function. + + +## Basic types and calls (and anything they rely on) + +Source_Code_Location +Context +Allocator +Logger + +__init_context +_cleanup_runtime + + +## cstring calls + +cstring_to_string +cstring_len + + + +## Required when RTTI is enabled (the vast majority of targets) + +Type_Info + +type_table +__type_info_of + + +## Hashing + +default_hasher +default_hasher_cstring +default_hasher_string + + +## Pseudo-CRT required procedured due to LLVM but useful in general +memset +memcpy +memove + + +## Procedures required by the LLVM backend +umodti3 +udivti3 +modti3 +divti3 +fixdfti +fixunsdfti +fixunsdfdi +floattidf +floattidf_unsigned +truncsfhf2 +truncdfhf2 +gnu_h2f_ieee +gnu_f2h_ieee +extendhfsf2 +__ashlti3 // wasm specific +__multi3 // wasm specific + + + +## Required an entry point is defined (i.e. 'main') + +args__ + + +## When -no-crt is defined (and not a wasm target) (mostly due to LLVM) +_tls_index +_fltused + + +## Bounds checking procedures (when not disabled with -no-bounds-check) + +bounds_check_error +matrix_bounds_check_error +slice_expr_error_hi +slice_expr_error_lo_hi +multi_pointer_slice_expr_error + + +## Type assertion check + +type_assertion_check +type_assertion_check2 // takes in typeid + + +## Arithmetic + +quo_complex32 +quo_complex64 +quo_complex128 + +mul_quaternion64 +mul_quaternion128 +mul_quaternion256 + +quo_quaternion64 +quo_quaternion128 +quo_quaternion256 + +abs_complex32 +abs_complex64 +abs_complex128 + +abs_quaternion64 +abs_quaternion128 +abs_quaternion256 + + +## Comparison + +memory_equal +memory_compare +memory_compare_zero + +cstring_eq +cstring_ne +cstring_lt +cstring_gt +cstring_le +cstring_gt + +string_eq +string_ne +string_lt +string_gt +string_le +string_gt + +complex32_eq +complex32_ne +complex64_eq +complex64_ne +complex128_eq +complex128_ne + +quaternion64_eq +quaternion64_ne +quaternion128_eq +quaternion128_ne +quaternion256_eq +quaternion256_ne + + +## Map specific calls + +map_seed_from_map_data +__dynamic_map_check_grow // static map calls +map_insert_hash_dynamic // static map calls +__dynamic_map_get // dynamic map calls +__dynamic_map_set // dynamic map calls + + +## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals) + +__dynamic_array_reserve +__dynamic_array_append + +__dynamic_map_reserve + + +## Objective-C specific + +objc_lookUpClass +sel_registerName +objc_allocateClassPair + + +## for-in `string` type + +string_decode_rune +string_decode_last_rune // #reverse for + +*/ \ No newline at end of file diff --git a/base/runtime/dynamic_array_internal.odin b/base/runtime/dynamic_array_internal.odin new file mode 100644 index 000000000..267ee0785 --- /dev/null +++ b/base/runtime/dynamic_array_internal.odin @@ -0,0 +1,138 @@ +package runtime + +__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 { + return true + } + + old_size := array.cap * elem_size + new_size := cap * elem_size + allocator := array.allocator + + new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) + if err != nil { + return false + } + if elem_size == 0 { + array.data = raw_data(new_data) + array.cap = cap + return true + } else if new_data != nil { + array.data = raw_data(new_data) + array.cap = min(cap, len(new_data)/elem_size) + return true + } + return false +} + +__dynamic_array_shrink :: proc(array_: rawptr, elem_size, elem_align: int, new_cap: int, loc := #caller_location) -> (did_shrink: 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 new_cap > array.cap { + return + } + + new_cap := new_cap + new_cap = max(new_cap, 0) + old_size := array.cap * elem_size + new_size := new_cap * elem_size + allocator := array.allocator + + new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) + if err != nil { + return + } + + array.data = raw_data(new_data) + array.len = min(new_cap, array.len) + array.cap = new_cap + return true +} + +__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 { + 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 { + return 0 + } + if item_count <= 0 { + 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 { + 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 { + 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 +} diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin new file mode 100644 index 000000000..491a7974d --- /dev/null +++ b/base/runtime/dynamic_map_internal.odin @@ -0,0 +1,924 @@ +package runtime + +import "core:intrinsics" +_ :: intrinsics + +// High performance, cache-friendly, open-addressed Robin Hood hashing hash map +// data structure with various optimizations for Odin. +// +// Copyright 2022 (c) Dale Weiler +// +// The core of the hash map data structure is the Raw_Map struct which is a +// type-erased representation of the map. This type-erased representation is +// used in two ways: static and dynamic. When static type information is known, +// the procedures suffixed with _static should be used instead of _dynamic. The +// static procedures are optimized since they have type information. Hashing of +// keys, comparison of keys, and data lookup are all optimized. When type +// information is not known, the procedures suffixed with _dynamic should be +// used. The representation of the map is the same for both static and dynamic, +// and procedures of each can be mixed and matched. The purpose of the dynamic +// representation is to enable reflection and runtime manipulation of the map. +// The dynamic procedures all take an additional Map_Info structure parameter +// which carries runtime values describing the size, alignment, and offset of +// various traits of a given key and value type pair. The Map_Info value can +// be created by calling map_info(K, V) with the key and value typeids. +// +// This map implementation makes extensive use of uintptr for representing +// sizes, lengths, capacities, masks, pointers, offsets, and addresses to avoid +// expensive sign extension and masking that would be generated if types were +// casted all over. The only place regular ints show up is in the cap() and +// len() implementations. +// +// To make this map cache-friendly it uses a novel strategy to ensure keys and +// values of the map are always cache-line aligned and that no single key or +// value of any type ever straddles a cache-line. This cache efficiency makes +// for quick lookups because the linear-probe always addresses data in a cache +// friendly way. This is enabled through the use of a special meta-type called +// a Map_Cell which packs as many values of a given type into a local array adding +// internal padding to round to MAP_CACHE_LINE_SIZE. One other benefit to storing +// the internal data in this manner is false sharing no longer occurs when using +// a map, enabling efficient concurrent access of the map data structure with +// minimal locking if desired. + +// With Robin Hood hashing a maximum load factor of 75% is ideal. +MAP_LOAD_FACTOR :: 75 + +// Minimum log2 capacity. +MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements + +// Has to be less than 100% though. +#assert(MAP_LOAD_FACTOR < 100) + +// This is safe to change. The log2 size of a cache-line. At minimum it has to +// be six though. Higher cache line sizes are permitted. +MAP_CACHE_LINE_LOG2 :: 6 + +// The size of a cache-line. +MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2 + +// The minimum cache-line size allowed by this implementation is 64 bytes since +// we need 6 bits in the base pointer to store the integer log2 capacity, which +// at maximum is 63. Odin uses signed integers to represent length and capacity, +// so only 63 bits are needed in the maximum case. +#assert(MAP_CACHE_LINE_SIZE >= 64) + +// Map_Cell type that packs multiple T in such a way to ensure that each T stays +// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0 +// +// This means a value of type T will never straddle a cache-line. +// +// When multiple Ts can fit in a single cache-line the data array will have more +// than one element. When it cannot, the data array will have one element and +// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE. +// +// We rely on the type system to do all the arithmetic and padding for us here. +// +// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit +// more involved as there now may be internal padding. The indexing now becomes +// +// N :: len(Map_Cell(T){}.data) +// i := index / N +// j := index % N +// cell[i].data[j] +// +// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some +// optimizations we can do to eliminate the need for any divisions as N will +// be bounded by [1, 64). +// +// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated +// as a regular array of T, which is the case for hashes. +Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) { + data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T, +} + +// So we can operate on a cell data structure at runtime without any type +// information, we have a simple table that stores some traits about the cell. +// +// 32-bytes on 64-bit +// 16-bytes on 32-bit +Map_Cell_Info :: struct { + size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits + align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits + size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits + elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits +} + +// map_cell_info :: proc "contextless" ($T: typeid) -> ^Map_Cell_Info {...} +map_cell_info :: intrinsics.type_map_cell_info + +// Same as the above procedure but at runtime with the cell Map_Cell_Info value. +@(require_results) +map_cell_index_dynamic :: #force_inline proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, index: uintptr) -> uintptr { + // Micro-optimize the common cases to save on integer division. + elements_per_cell := uintptr(info.elements_per_cell) + size_of_cell := uintptr(info.size_of_cell) + switch elements_per_cell { + case 1: + return base + (index * size_of_cell) + case 2: + cell_index := index >> 1 + data_index := index & 1 + size_of_type := uintptr(info.size_of_type) + return base + (cell_index * size_of_cell) + (data_index * size_of_type) + case: + cell_index := index / elements_per_cell + data_index := index % elements_per_cell + size_of_type := uintptr(info.size_of_type) + return base + (cell_index * size_of_cell) + (data_index * size_of_type) + } +} + +// Same as above procedure but with compile-time constant index. +@(require_results) +map_cell_index_dynamic_const :: proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, $INDEX: uintptr) -> uintptr { + elements_per_cell := uintptr(info.elements_per_cell) + size_of_cell := uintptr(info.size_of_cell) + size_of_type := uintptr(info.size_of_type) + cell_index := INDEX / elements_per_cell + data_index := INDEX % elements_per_cell + return base + (cell_index * size_of_cell) + (data_index * size_of_type) +} + +// We always round the capacity to a power of two so this becomes [16]Foo, which +// works out to [4]Cell(Foo). +// +// The following compile-time procedure indexes such a [N]Cell(T) structure as +// if it were a flat array accounting for the internal padding introduced by the +// Cell structure. +@(require_results) +map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T), index: uintptr) -> ^T #no_bounds_check { + N :: size_of(Map_Cell(T){}.data) / size_of(T) when size_of(T) > 0 else 1 + + #assert(N <= MAP_CACHE_LINE_SIZE) + + when size_of(Map_Cell(T)) == size_of([N]T) { + // No padding case, can treat as a regular array of []T. + + return &([^]T)(cells)[index] + } else when (N & (N - 1)) == 0 && N <= 8*size_of(uintptr) { + // Likely case, N is a power of two because T is a power of two. + + // Compute the integer log 2 of N, this is the shift amount to index the + // correct cell. Odin's intrinsics.count_leading_zeros does not produce a + // constant, hence this approach. We only need to check up to N = 64. + SHIFT :: 1 when N < 2 else + 2 when N < 4 else + 3 when N < 8 else + 4 when N < 16 else + 5 when N < 32 else 6 + #assert(SHIFT <= MAP_CACHE_LINE_LOG2) + // Unique case, no need to index data here since only one element. + when N == 1 { + return &cells[index >> SHIFT].data[0] + } else { + return &cells[index >> SHIFT].data[index & (N - 1)] + } + } else { + // Least likely (and worst case), we pay for a division operation but we + // assume the compiler does not actually generate a division. N will be in the + // range [1, CACHE_LINE_SIZE) and not a power of two. + return &cells[index / N].data[index % N] + } +} + +// len() for map +@(require_results) +map_len :: #force_inline proc "contextless" (m: Raw_Map) -> int { + return int(m.len) +} + +// cap() for map +@(require_results) +map_cap :: #force_inline proc "contextless" (m: Raw_Map) -> int { + // The data uintptr stores the capacity in the lower six bits which gives the + // a maximum value of 2^6-1, or 63. We store the integer log2 of capacity + // since our capacity is always a power of two. We only need 63 bits as Odin + // represents length and capacity as a signed integer. + return 0 if m.data == 0 else 1 << map_log2_cap(m) +} + +// Query the load factor of the map. This is not actually configurable, but +// some math is needed to compute it. Compute it as a fixed point percentage to +// avoid floating point operations. This division can be optimized out by +// multiplying by the multiplicative inverse of 100. +@(require_results) +map_load_factor :: #force_inline proc "contextless" (log2_capacity: uintptr) -> uintptr { + return ((uintptr(1) << log2_capacity) * MAP_LOAD_FACTOR) / 100 +} + +@(require_results) +map_resize_threshold :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return map_load_factor(map_log2_cap(m)) +} + +// The data stores the log2 capacity in the lower six bits. This is primarily +// used in the implementation rather than map_cap since the check for data = 0 +// isn't necessary in the implementation. cap() on the otherhand needs to work +// when called on an empty map. +@(require_results) +map_log2_cap :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return m.data & (64 - 1) +} + +// Canonicalize the data by removing the tagged capacity stored in the lower six +// bits of the data uintptr. +@(require_results) +map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return m.data &~ uintptr(64 - 1) +} + + +Map_Hash :: uintptr + +TOMBSTONE_MASK :: 1<<(size_of(Map_Hash)*8 - 1) + +// Procedure to check if a slot is empty for a given hash. This is represented +// by the zero value to make the zero value useful. This is a procedure just +// for prose reasons. +@(require_results) +map_hash_is_empty :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { + return hash == 0 +} + +@(require_results) +map_hash_is_deleted :: #force_no_inline proc "contextless" (hash: Map_Hash) -> bool { + // The MSB indicates a tombstone + return hash & TOMBSTONE_MASK != 0 +} +@(require_results) +map_hash_is_valid :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { + // The MSB indicates a tombstone + return (hash != 0) & (hash & TOMBSTONE_MASK == 0) +} + +@(require_results) +map_seed :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return map_seed_from_map_data(map_data(m)) +} + +// splitmix for uintptr +@(require_results) +map_seed_from_map_data :: #force_inline proc "contextless" (data: uintptr) -> uintptr { + when size_of(uintptr) == size_of(u64) { + mix := data + 0x9e3779b97f4a7c15 + mix = (mix ~ (mix >> 30)) * 0xbf58476d1ce4e5b9 + mix = (mix ~ (mix >> 27)) * 0x94d049bb133111eb + return mix ~ (mix >> 31) + } else { + mix := data + 0x9e3779b9 + mix = (mix ~ (mix >> 16)) * 0x21f0aaad + mix = (mix ~ (mix >> 15)) * 0x735a2d97 + return mix ~ (mix >> 15) + } +} + +// Computes the desired position in the array. This is just index % capacity, +// but a procedure as there's some math involved here to recover the capacity. +@(require_results) +map_desired_position :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash) -> uintptr { + // We do not use map_cap since we know the capacity will not be zero here. + capacity := uintptr(1) << map_log2_cap(m) + return uintptr(hash & Map_Hash(capacity - 1)) +} + +@(require_results) +map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash, slot: uintptr) -> uintptr { + // We do not use map_cap since we know the capacity will not be zero here. + capacity := uintptr(1) << map_log2_cap(m) + return (slot + capacity - map_desired_position(m, hash)) & (capacity - 1) +} + +// When working with the type-erased structure at runtime we need information +// about the map to make working with it possible. This info structure stores +// that. +// +// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be +// modified after creation +// +// 32-bytes on 64-bit +// 16-bytes on 32-bit +Map_Info :: struct { + ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit + vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit + key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit + key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit +} + + +// The Map_Info structure is basically a pseudo-table of information for a given K and V pair. +// map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...} +map_info :: intrinsics.type_map_info + +@(require_results) +map_kvh_data_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (ks: uintptr, vs: uintptr, hs: [^]Map_Hash, sk: uintptr, sv: uintptr) { + INFO_HS := intrinsics.type_map_cell_info(Map_Hash) + + capacity := uintptr(1) << map_log2_cap(m) + ks = map_data(m) + vs = map_cell_index_dynamic(ks, info.ks, capacity) // Skip past ks to get start of vs + hs_ := map_cell_index_dynamic(vs, info.vs, capacity) // Skip past vs to get start of hs + sk = map_cell_index_dynamic(hs_, INFO_HS, capacity) // Skip past hs to get start of sk + // Need to skip past two elements in the scratch key space to get to the start + // of the scratch value space, of which there's only two elements as well. + sv = map_cell_index_dynamic_const(sk, info.ks, 2) + + hs = ([^]Map_Hash)(hs_) + return +} + +@(require_results) +map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (vs: uintptr) { + capacity := uintptr(1) << map_log2_cap(m) + return map_cell_index_dynamic(map_data(m), info.ks, capacity) // Skip past ks to get start of vs +} + + +@(private, require_results) +map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr { + round :: #force_inline proc "contextless" (value: uintptr) -> uintptr { + CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 + return (value + CACHE_MASK) &~ CACHE_MASK + } + INFO_HS := intrinsics.type_map_cell_info(Map_Hash) + + size := uintptr(0) + size = round(map_cell_index_dynamic(size, info.ks, capacity)) + size = round(map_cell_index_dynamic(size, info.vs, capacity)) + size = round(map_cell_index_dynamic(size, INFO_HS, capacity)) + size = round(map_cell_index_dynamic(size, info.ks, 2)) // Two additional ks for scratch storage + size = round(map_cell_index_dynamic(size, info.vs, 2)) // Two additional vs for scratch storage + return size +} + +// The only procedure which needs access to the context is the one which allocates the map. +@(require_results) +map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) { + result.allocator = allocator // set the allocator always + if log2_capacity == 0 { + return + } + + if log2_capacity >= 64 { + // Overflowed, would be caused by log2_capacity > 64 + return {}, .Out_Of_Memory + } + + capacity := uintptr(1) << max(log2_capacity, MAP_MIN_LOG2_CAPACITY) + + CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 + + size := map_total_allocation_size(capacity, info) + + data := mem_alloc_non_zeroed(int(size), MAP_CACHE_LINE_SIZE, allocator, loc) or_return + data_ptr := uintptr(raw_data(data)) + if data_ptr == 0 { + err = .Out_Of_Memory + return + } + if intrinsics.expect(data_ptr & CACHE_MASK != 0, false) { + panic("allocation not aligned to a cache line", loc) + } else { + result.data = data_ptr | log2_capacity // Tagged pointer representation for capacity. + result.len = 0 + + map_clear_dynamic(&result, info) + } + return +} + +// This procedure has to stack allocate storage to store local keys during the +// Robin Hood hashing technique where elements are swapped in the backing +// arrays to reduce variance. This swapping can only be done with memcpy since +// there is no type information. +// +// This procedure returns the address of the just inserted value. +@(require_results) +map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { + h := h + pos := map_desired_position(m^, h) + distance := uintptr(0) + mask := (uintptr(1) << map_log2_cap(m^)) - 1 + + ks, vs, hs, sk, sv := map_kvh_data_dynamic(m^, info) + + // Avoid redundant loads of these values + size_of_k := info.ks.size_of_type + size_of_v := info.vs.size_of_type + + k := map_cell_index_dynamic(sk, info.ks, 0) + v := map_cell_index_dynamic(sv, info.vs, 0) + intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(ik), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(iv), size_of_v) + + // Temporary k and v dynamic storage for swap below + tk := map_cell_index_dynamic(sk, info.ks, 1) + tv := map_cell_index_dynamic(sv, info.vs, 1) + + swap_loop: for { + element_hash := hs[pos] + + if map_hash_is_empty(element_hash) { + k_dst := map_cell_index_dynamic(ks, info.ks, pos) + v_dst := map_cell_index_dynamic(vs, info.vs, pos) + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + return result if result != 0 else v_dst + } + + if map_hash_is_deleted(element_hash) { + break swap_loop + } + + if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance { + if result == 0 { + result = map_cell_index_dynamic(vs, info.vs, pos) + } + + kp := map_cell_index_dynamic(ks, info.ks, pos) + vp := map_cell_index_dynamic(vs, info.vs, pos) + + intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k) + + intrinsics.mem_copy_non_overlapping(rawptr(tv), rawptr(v), size_of_v) + intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(vp), size_of_v) + intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(tv), size_of_v) + + th := h + h = hs[pos] + hs[pos] = th + + distance = probe_distance + } + + pos = (pos + 1) & mask + distance += 1 + } + + // backward shift loop + hs[pos] = 0 + look_ahead: uintptr = 1 + for { + la_pos := (pos + look_ahead) & mask + element_hash := hs[la_pos] + + if map_hash_is_deleted(element_hash) { + look_ahead += 1 + hs[la_pos] = 0 + continue + } + + k_dst := map_cell_index_dynamic(ks, info.ks, pos) + v_dst := map_cell_index_dynamic(vs, info.vs, pos) + + if map_hash_is_empty(element_hash) { + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + return result if result != 0 else v_dst + } + + k_src := map_cell_index_dynamic(ks, info.ks, la_pos) + v_src := map_cell_index_dynamic(vs, info.vs, la_pos) + probe_distance := map_probe_distance(m^, element_hash, la_pos) + + if probe_distance < look_ahead { + // probed can be made ideal while placing saved (ending condition) + if result == 0 { + result = v_dst + } + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + // This will be an ideal move + pos = (la_pos - probe_distance) & mask + look_ahead -= probe_distance + + // shift until we hit ideal/empty + for probe_distance != 0 { + k_dst = map_cell_index_dynamic(ks, info.ks, pos) + v_dst = map_cell_index_dynamic(vs, info.vs, pos) + + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) + hs[pos] = element_hash + hs[la_pos] = 0 + + pos = (pos + 1) & mask + la_pos = (la_pos + 1) & mask + look_ahead = (la_pos - pos) & mask + element_hash = hs[la_pos] + if map_hash_is_empty(element_hash) { + return + } + + probe_distance = map_probe_distance(m^, element_hash, la_pos) + if probe_distance == 0 { + return + } + // can be ideal? + if probe_distance < look_ahead { + pos = (la_pos - probe_distance) & mask + } + k_src = map_cell_index_dynamic(ks, info.ks, la_pos) + v_src = map_cell_index_dynamic(vs, info.vs, la_pos) + } + return + } else if distance < probe_distance - look_ahead { + // shift back probed + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) + hs[pos] = element_hash + hs[la_pos] = 0 + } else { + // place saved, save probed + if result == 0 { + result = v_dst + } + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(k_src), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(v_src), size_of_v) + h = hs[la_pos] + hs[la_pos] = 0 + distance = probe_distance - look_ahead + } + + pos = (pos + 1) & mask + distance += 1 + } +} + +@(require_results) +map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { + log2_capacity := map_log2_cap(m^) + new_capacity := uintptr(1) << max(log2_capacity + 1, MAP_MIN_LOG2_CAPACITY) + return map_reserve_dynamic(m, info, new_capacity, loc) +} + + +@(require_results) +map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { + @(require_results) + ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { + z := intrinsics.count_leading_zeros(x) + if z > 0 && x & (x-1) != 0 { + z -= 1 + } + return size_of(uintptr)*8 - 1 - z + } + + if m.allocator.procedure == nil { + m.allocator = context.allocator + } + + new_capacity := new_capacity + old_capacity := uintptr(map_cap(m^)) + + if old_capacity >= new_capacity { + return nil + } + + // ceiling nearest power of two + log2_new_capacity := ceil_log2(new_capacity) + + log2_min_cap := max(MAP_MIN_LOG2_CAPACITY, log2_new_capacity) + + if m.data == 0 { + m^ = map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return + return nil + } + + resized := map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return + + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + + // Cache these loads to avoid hitting them in the for loop. + n := m.len + for i in 0.. (did_shrink: bool, err: Allocator_Error) { + if m.allocator.procedure == nil { + m.allocator = context.allocator + } + + // Cannot shrink the capacity if the number of items in the map would exceed + // one minus the current log2 capacity's resize threshold. That is the shrunk + // map needs to be within the max load factor. + log2_capacity := map_log2_cap(m^) + if uintptr(m.len) >= map_load_factor(log2_capacity - 1) { + return false, nil + } + + shrunk := map_alloc_dynamic(info, log2_capacity - 1, m.allocator) or_return + + capacity := uintptr(1) << log2_capacity + + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + + n := m.len + for i in 0.. Allocator_Error { + ptr := rawptr(map_data(m)) + size := int(map_total_allocation_size(uintptr(map_cap(m)), info)) + err := mem_free_with_size(ptr, size, m.allocator, loc) + #partial switch err { + case .None, .Mode_Not_Implemented: + return nil + } + return err +} + +@(require_results) +map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { + if map_len(m) == 0 { + return 0, false + } + h := info.key_hasher(rawptr(k), map_seed(m)) + p := map_desired_position(m, h) + d := uintptr(0) + c := (uintptr(1) << map_log2_cap(m)) - 1 + ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) + for { + element_hash := hs[p] + if map_hash_is_empty(element_hash) { + return 0, false + } else if d > map_probe_distance(m, element_hash, p) { + return 0, false + } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { + return p, true + } + p = (p + 1) & c + d += 1 + } +} +@(require_results) +map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { + if map_len(m) == 0 { + return false + } + h := info.key_hasher(rawptr(k), map_seed(m)) + p := map_desired_position(m, h) + d := uintptr(0) + c := (uintptr(1) << map_log2_cap(m)) - 1 + ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) + for { + element_hash := hs[p] + if map_hash_is_empty(element_hash) { + return false + } else if d > map_probe_distance(m, element_hash, p) { + return false + } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { + return true + } + p = (p + 1) & c + d += 1 + } +} + + + +@(require_results) +map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { + index := map_lookup_dynamic(m^, info, k) or_return + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + hs[index] |= TOMBSTONE_MASK + old_k = map_cell_index_dynamic(ks, info.ks, index) + old_v = map_cell_index_dynamic(vs, info.vs, index) + m.len -= 1 + ok = true + + mask := (uintptr(1)< (ks: [^]Map_Cell(K), vs: [^]Map_Cell(V), hs: [^]Map_Hash) { + capacity := uintptr(cap(m)) + ks = ([^]Map_Cell(K))(map_data(transmute(Raw_Map)m)) + vs = ([^]Map_Cell(V))(map_cell_index_static(ks, capacity)) + hs = ([^]Map_Hash)(map_cell_index_static(vs, capacity)) + return +} + + +@(require_results) +map_get :: proc "contextless" (m: $T/map[$K]$V, key: K) -> (stored_key: K, stored_value: V, ok: bool) { + rm := transmute(Raw_Map)m + if rm.len == 0 { + return + } + info := intrinsics.type_map_info(T) + key := key + + h := info.key_hasher(&key, map_seed(rm)) + pos := map_desired_position(rm, h) + distance := uintptr(0) + mask := (uintptr(1) << map_log2_cap(rm)) - 1 + ks, vs, hs := map_kvh_data_static(m) + for { + element_hash := hs[pos] + if map_hash_is_empty(element_hash) { + return + } else if distance > map_probe_distance(rm, element_hash, pos) { + return + } else if element_hash == h { + element_key := map_cell_index_static(ks, pos) + if info.key_equal(&key, rawptr(element_key)) { + element_value := map_cell_index_static(vs, pos) + stored_key = (^K)(element_key)^ + stored_value = (^V)(element_value)^ + ok = true + return + } + + } + pos = (pos + 1) & mask + distance += 1 + } +} + +// IMPORTANT: USED WITHIN THE COMPILER +__dynamic_map_get :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, key: rawptr) -> (ptr: rawptr) { + if m.len == 0 { + return nil + } + pos := map_desired_position(m^, h) + distance := uintptr(0) + mask := (uintptr(1) << map_log2_cap(m^)) - 1 + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + for { + element_hash := hs[pos] + if map_hash_is_empty(element_hash) { + return nil + } else if distance > map_probe_distance(m^, element_hash, pos) { + return nil + } else if element_hash == h && info.key_equal(key, rawptr(map_cell_index_dynamic(ks, info.ks, pos))) { + return rawptr(map_cell_index_dynamic(vs, info.vs, pos)) + } + pos = (pos + 1) & mask + distance += 1 + } +} + +// IMPORTANT: USED WITHIN THE COMPILER +__dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) { + if m.len >= map_resize_threshold(m^) { + return map_grow_dynamic(m, info, loc), true + } + return nil, false +} + +__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> rawptr { + return __dynamic_map_set(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc) +} + + +// IMPORTANT: USED WITHIN THE COMPILER +__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> rawptr { + if found := __dynamic_map_get(m, info, hash, key); found != nil { + intrinsics.mem_copy_non_overlapping(found, value, info.vs.size_of_type) + return found + } + + hash := hash + err, has_grown := __dynamic_map_check_grow(m, info, loc) + if err != nil { + return nil + } + if has_grown { + hash = info.key_hasher(key, map_seed(m^)) + } + + result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value)) + m.len += 1 + return rawptr(result) +} + +// IMPORTANT: USED WITHIN THE COMPILER +@(private) +__dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error { + return map_reserve_dynamic(m, info, uintptr(new_capacity), loc) +} + + + +// NOTE: the default hashing algorithm derives from fnv64a, with some minor modifications to work for `map` type: +// +// * Convert a `0` result to `1` +// * "empty entry" +// * Prevent the top bit from being set +// * "deleted entry" +// +// Both of these modification are necessary for the implementation of the `map` + +INITIAL_HASH_SEED :: 0xcbf29ce484222325 + +HASH_MASK :: 1 << (8*size_of(uintptr) - 1) -1 + +default_hasher :: #force_inline proc "contextless" (data: rawptr, seed: uintptr, N: int) -> uintptr { + h := u64(seed) + INITIAL_HASH_SEED + p := ([^]byte)(data) + for _ in 0.. uintptr { + str := (^[]byte)(data) + return default_hasher(raw_data(str^), seed, len(str)) +} +default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr { + h := u64(seed) + INITIAL_HASH_SEED + if ptr := (^[^]byte)(data)^; ptr != nil { + for ptr[0] != 0 { + h = (h ~ u64(ptr[0])) * 0x100000001b3 + ptr = ptr[1:] + } + } + h &= HASH_MASK + return uintptr(h) | uintptr(uintptr(h) == 0) +} diff --git a/base/runtime/entry_unix.odin b/base/runtime/entry_unix.odin new file mode 100644 index 000000000..f494a509e --- /dev/null +++ b/base/runtime/entry_unix.odin @@ -0,0 +1,59 @@ +//+private +//+build linux, darwin, freebsd, openbsd +//+no-instrumentation +package runtime + +import "core:intrinsics" + +when ODIN_BUILD_MODE == .Dynamic { + @(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/) + _odin_entry_point :: proc "c" () { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(link_name="_odin_exit_point", linkage="strong", require/*, link_section=".fini"*/) + _odin_exit_point :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } + @(link_name="main", linkage="strong", require) + main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { + return 0 + } +} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { + when ODIN_NO_CRT { + // NOTE(flysand): We need to start from assembly because we need + // to retrieve argc and argv from the stack + when ODIN_ARCH == .amd64 { + @require foreign import entry "entry_unix_no_crt_amd64.asm" + SYS_exit :: 60 + } else when ODIN_ARCH == .i386 { + @require foreign import entry "entry_unix_no_crt_i386.asm" + SYS_exit :: 1 + } else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { + @require foreign import entry "entry_unix_no_crt_darwin_arm64.asm" + SYS_exit :: 1 + } + @(link_name="_start_odin", linkage="strong", require) + _start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! { + args__ = argv[:argc] + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + intrinsics.syscall(SYS_exit, 0) + unreachable() + } + } else { + @(link_name="main", linkage="strong", require) + main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { + args__ = argv[:argc] + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + return 0 + } + } +} diff --git a/base/runtime/entry_unix_no_crt_amd64.asm b/base/runtime/entry_unix_no_crt_amd64.asm new file mode 100644 index 000000000..f0bdce8d7 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_amd64.asm @@ -0,0 +1,43 @@ +bits 64 + +extern _start_odin +global _start + +section .text + +;; Entry point for programs that specify -no-crt option +;; This entry point should be compatible with dynamic loaders on linux +;; The parameters the dynamic loader passes to the _start function: +;; RDX = pointer to atexit function +;; The stack layout is as follows: +;; +-------------------+ +;; NULL +;; +-------------------+ +;; envp[m] +;; +-------------------+ +;; ... +;; +-------------------+ +;; envp[0] +;; +-------------------+ +;; NULL +;; +-------------------+ +;; argv[n] +;; +-------------------+ +;; ... +;; +-------------------+ +;; argv[0] +;; +-------------------+ +;; argc +;; +-------------------+ <------ RSP +;; +_start: + ;; Mark stack frame as the top of the stack + xor rbp, rbp + ;; Load argc into 1st param reg, argv into 2nd param reg + pop rdi + mov rdx, rsi + ;; Align stack pointer down to 16-bytes (sysv calling convention) + and rsp, -16 + ;; Call into odin entry point + call _start_odin + jmp $$ \ No newline at end of file diff --git a/base/runtime/entry_unix_no_crt_darwin_arm64.asm b/base/runtime/entry_unix_no_crt_darwin_arm64.asm new file mode 100644 index 000000000..0f71fbdf8 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_darwin_arm64.asm @@ -0,0 +1,20 @@ + .section __TEXT,__text + + ; NOTE(laytan): this should ideally be the -minimum-os-version flag but there is no nice way of preprocessing assembly in Odin. + ; 10 seems to be the lowest it goes and I don't see it mess with any targeted os version so this seems fine. + .build_version macos, 10, 0 + + .extern __start_odin + + .global _main + .align 2 +_main: + mov x5, sp ; use x5 as the stack pointer + + str x0, [x5] ; get argc into x0 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) + str x1, [x5, #8] ; get argv into x1 + + and sp, x5, #~15 ; force 16-byte alignment of the stack + + bl __start_odin ; call into Odin entry point + ret ; should never get here diff --git a/base/runtime/entry_unix_no_crt_i386.asm b/base/runtime/entry_unix_no_crt_i386.asm new file mode 100644 index 000000000..a61d56a16 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_i386.asm @@ -0,0 +1,18 @@ +bits 32 + +extern _start_odin +global _start + +section .text + +;; NOTE(flysand): For description see the corresponding *_amd64.asm file +;; also I didn't test this on x86-32 +_start: + xor ebp, rbp + pop ecx + mov eax, esp + and esp, -16 + push eax + push ecx + call _start_odin + jmp $$ \ No newline at end of file diff --git a/base/runtime/entry_wasm.odin b/base/runtime/entry_wasm.odin new file mode 100644 index 000000000..e7f3f156f --- /dev/null +++ b/base/runtime/entry_wasm.odin @@ -0,0 +1,20 @@ +//+private +//+build wasm32, wasm64p32 +//+no-instrumentation +package runtime + +import "core:intrinsics" + +when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { + @(link_name="_start", linkage="strong", require, export) + _start :: proc "c" () { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(link_name="_end", linkage="strong", require, export) + _end :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } +} \ No newline at end of file diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin new file mode 100644 index 000000000..b6fbe1dcc --- /dev/null +++ b/base/runtime/entry_windows.odin @@ -0,0 +1,50 @@ +//+private +//+build windows +//+no-instrumentation +package runtime + +import "core:intrinsics" + +when ODIN_BUILD_MODE == .Dynamic { + @(link_name="DllMain", linkage="strong", require) + DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { + context = default_context() + + // Populate Windows DLL-specific global + dll_forward_reason = DLL_Forward_Reason(fdwReason) + + switch dll_forward_reason { + case .Process_Attach: + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + case .Process_Detach: + #force_no_inline _cleanup_runtime() + case .Thread_Attach: + break + case .Thread_Detach: + break + } + return true + } +} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { + when ODIN_ARCH == .i386 || ODIN_NO_CRT { + @(link_name="mainCRTStartup", linkage="strong", require) + mainCRTStartup :: proc "system" () -> i32 { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + return 0 + } + } else { + @(link_name="main", linkage="strong", require) + main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { + args__ = argv[:argc] + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + return 0 + } + } +} \ No newline at end of file diff --git a/base/runtime/error_checks.odin b/base/runtime/error_checks.odin new file mode 100644 index 000000000..ea6333c29 --- /dev/null +++ b/base/runtime/error_checks.odin @@ -0,0 +1,292 @@ +package runtime + +@(no_instrumentation) +bounds_trap :: proc "contextless" () -> ! { + when ODIN_OS == .Windows { + windows_trap_array_bounds() + } else { + trap() + } +} + +@(no_instrumentation) +type_assertion_trap :: proc "contextless" () -> ! { + when ODIN_OS == .Windows { + windows_trap_type_assertion() + } else { + trap() + } +} + + +bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) { + if uint(index) < uint(count) { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Index ") + print_i64(i64(index)) + print_string(" is out of range 0..<") + print_i64(i64(count)) + print_byte('\n') + bounds_trap() + } + handle_error(file, line, column, index, count) +} + +@(no_instrumentation) +slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid slice indices ") + print_i64(i64(lo)) + print_string(":") + print_i64(i64(hi)) + print_string(" is out of range 0..<") + print_i64(i64(len)) + print_byte('\n') + bounds_trap() +} + +@(no_instrumentation) +multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid slice indices ") + print_i64(i64(lo)) + print_string(":") + print_i64(i64(hi)) + print_byte('\n') + bounds_trap() +} + + +multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) { + if lo <= hi { + return + } + multi_pointer_slice_handle_error(file, line, column, lo, hi) +} + +slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) { + if 0 <= hi && hi <= len { + return + } + slice_handle_error(file, line, column, 0, hi, len) +} + +slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) { + if 0 <= lo && lo <= len && lo <= hi && hi <= len { + return + } + slice_handle_error(file, line, column, lo, hi, len) +} + +dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) { + if 0 <= low && low <= high && high <= max { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid dynamic array indices ") + print_i64(i64(low)) + print_string(":") + print_i64(i64(high)) + print_string(" is out of range 0..<") + print_i64(i64(max)) + print_byte('\n') + bounds_trap() + } + handle_error(file, line, column, low, high, max) +} + + +matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) { + if uint(row_index) < uint(row_count) && + uint(column_index) < uint(column_count) { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Matrix indices [") + print_i64(i64(row_index)) + print_string(", ") + print_i64(i64(column_index)) + print_string(" is out of range [0..<") + print_i64(i64(row_count)) + print_string(", 0..<") + print_i64(i64(column_count)) + print_string("]") + print_byte('\n') + bounds_trap() + } + handle_error(file, line, column, row_index, column_index, row_count, column_count) +} + + +when ODIN_NO_RTTI { + type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32) { + if ok { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion\n") + type_assertion_trap() + } + handle_error(file, line, column) + } + + type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32) { + if ok { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion\n") + type_assertion_trap() + } + handle_error(file, line, column) + } +} else { + type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) { + if ok { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion from ") + print_typeid(from) + print_string(" to ") + print_typeid(to) + print_byte('\n') + type_assertion_trap() + } + handle_error(file, line, column, from, to) + } + + type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) { + if ok { + return + } + + variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid { + if id == nil || data == nil { + return id + } + ti := type_info_base(type_info_of(id)) + #partial switch v in ti.variant { + case Type_Info_Any: + return (^any)(data).id + case Type_Info_Union: + tag_ptr := uintptr(data) + v.tag_offset + idx := 0 + switch v.tag_type.size { + case 1: idx = int((^u8)(tag_ptr)^) - 1 + case 2: idx = int((^u16)(tag_ptr)^) - 1 + case 4: idx = int((^u32)(tag_ptr)^) - 1 + case 8: idx = int((^u64)(tag_ptr)^) - 1 + case 16: idx = int((^u128)(tag_ptr)^) - 1 + } + if idx < 0 { + return nil + } else if idx < len(v.variants) { + return v.variants[idx].id + } + } + return id + } + + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! { + + actual := variant_type(from, from_data) + + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion from ") + print_typeid(from) + print_string(" to ") + print_typeid(to) + if actual != from { + print_string(", actual type: ") + print_typeid(actual) + } + print_byte('\n') + type_assertion_trap() + } + handle_error(file, line, column, from, to, from_data) + } +} + + +make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) { + if 0 <= len { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! { + print_caller_location(loc) + print_string(" Invalid slice length for make: ") + print_i64(i64(len)) + print_byte('\n') + bounds_trap() + } + handle_error(loc, len) +} + +make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) { + if 0 <= len && len <= cap { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) -> ! { + print_caller_location(loc) + print_string(" Invalid dynamic array parameters for make: ") + print_i64(i64(len)) + print_byte(':') + print_i64(i64(cap)) + print_byte('\n') + bounds_trap() + } + handle_error(loc, len, cap) +} + +make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) { + if 0 <= cap { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) -> ! { + print_caller_location(loc) + print_string(" Invalid map capacity for make: ") + print_i64(i64(cap)) + print_byte('\n') + bounds_trap() + } + handle_error(loc, cap) +} + + + + + +bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) { + bounds_check_error(loc.file_path, loc.line, loc.column, index, count) +} + +slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) { + slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len) +} + +slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) { + slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len) +} + +dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) { + dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max) +} diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin new file mode 100644 index 000000000..a03c2a701 --- /dev/null +++ b/base/runtime/internal.odin @@ -0,0 +1,1036 @@ +package runtime + +import "core:intrinsics" + +@(private="file") +IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 + +@(private) +RUNTIME_LINKAGE :: "strong" when ( + (ODIN_USE_SEPARATE_MODULES || + ODIN_BUILD_MODE == .Dynamic || + !ODIN_NO_CRT) && + !IS_WASM) else "internal" +RUNTIME_REQUIRE :: !ODIN_TILDE + +@(private) +__float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16 + + +@(private) +byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check { + return ([^]byte)(data)[:max(len, 0)] +} + +is_power_of_two_int :: #force_inline proc(x: int) -> bool { + if x <= 0 { + return false + } + return (x & (x-1)) == 0 +} + +align_forward_int :: #force_inline proc(ptr, align: int) -> int { + assert(is_power_of_two_int(align)) + + p := ptr + modulo := p & (align-1) + if modulo != 0 { + p += align - modulo + } + return p +} + +is_power_of_two_uintptr :: #force_inline proc(x: uintptr) -> bool { + if x <= 0 { + return false + } + return (x & (x-1)) == 0 +} + +align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr { + assert(is_power_of_two_uintptr(align)) + + p := ptr + modulo := p & (align-1) + if modulo != 0 { + p += align - modulo + } + return p +} + +mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { + if data == nil { + return nil + } + if len <= 0 { + return data + } + intrinsics.mem_zero(data, len) + return data +} + +mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { + if src != nil && dst != src && len > 0 { + // NOTE(bill): This _must_ be implemented like C's memmove + intrinsics.mem_copy(dst, src, len) + } + return dst +} + +mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { + if src != nil && dst != src && len > 0 { + // NOTE(bill): This _must_ be implemented like C's memcpy + intrinsics.mem_copy_non_overlapping(dst, src, len) + } + return dst +} + +DEFAULT_ALIGNMENT :: 2*align_of(rawptr) + +mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + if size == 0 { + return nil, nil + } + if allocator.procedure == nil { + return nil, nil + } + return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) +} + +mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + if size == 0 || allocator.procedure == nil { + return nil, nil + } + return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) +} + +mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + if size == 0 || allocator.procedure == nil { + return nil, nil + } + return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, size, alignment, nil, 0, loc) +} + +mem_free :: #force_inline proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + if ptr == nil || allocator.procedure == nil { + return nil + } + _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, 0, loc) + return err +} + +mem_free_with_size :: #force_inline proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + if ptr == nil || allocator.procedure == nil { + return nil + } + _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, byte_count, loc) + return err +} + +mem_free_bytes :: #force_inline proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + if bytes == nil || allocator.procedure == nil { + return nil + } + _, err := allocator.procedure(allocator.data, .Free, 0, 0, raw_data(bytes), len(bytes), loc) + return err +} + + +mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { + if allocator.procedure != nil { + _, err = allocator.procedure(allocator.data, .Free_All, 0, 0, nil, 0, loc) + } + return +} + +_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + if allocator.procedure == nil { + return nil, nil + } + if new_size == 0 { + if ptr != nil { + _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) + return + } + return + } else if ptr == nil { + if should_zero { + return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + } else { + return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) + } + } else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 { + data = ([^]byte)(ptr)[:old_size] + return + } + + if should_zero { + data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc) + } else { + data, err = allocator.procedure(allocator.data, .Resize_Non_Zeroed, new_size, alignment, ptr, old_size, loc) + } + if err == .Mode_Not_Implemented { + if should_zero { + data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + } else { + data, err = allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) + } + if err != nil { + return + } + copy(data, ([^]byte)(ptr)[:old_size]) + _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) + } + return +} + +mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc) +} +non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) +} + +memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool { + switch { + case n == 0: return true + case x == y: return true + } + a, b := ([^]byte)(x), ([^]byte)(y) + length := uint(n) + + for i := uint(0); i < length; i += 1 { + if a[i] != b[i] { + return false + } + } + return true + +/* + + when size_of(uint) == 8 { + if word_length := length >> 3; word_length != 0 { + for _ in 0..> 2; word_length != 0 { + for _ in 0.. int #no_bounds_check { + switch { + case a == b: return 0 + case a == nil: return -1 + case b == nil: return +1 + } + + x := uintptr(a) + y := uintptr(b) + n := uintptr(n) + + SU :: size_of(uintptr) + fast := n/SU + 1 + offset := (fast-1)*SU + curr_block := uintptr(0) + if n < SU { + fast = 0 + } + + for /**/; curr_block < fast; curr_block += 1 { + va := (^uintptr)(x + curr_block * size_of(uintptr))^ + vb := (^uintptr)(y + curr_block * size_of(uintptr))^ + if va ~ vb != 0 { + for pos := curr_block*SU; pos < n; pos += 1 { + a := (^byte)(x+pos)^ + b := (^byte)(y+pos)^ + if a ~ b != 0 { + return -1 if (int(a) - int(b)) < 0 else +1 + } + } + } + } + + for /**/; offset < n; offset += 1 { + a := (^byte)(x+offset)^ + b := (^byte)(y+offset)^ + if a ~ b != 0 { + return -1 if (int(a) - int(b)) < 0 else +1 + } + } + + return 0 +} + +memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check { + x := uintptr(a) + n := uintptr(n) + + SU :: size_of(uintptr) + fast := n/SU + 1 + offset := (fast-1)*SU + curr_block := uintptr(0) + if n < SU { + fast = 0 + } + + for /**/; curr_block < fast; curr_block += 1 { + va := (^uintptr)(x + curr_block * size_of(uintptr))^ + if va ~ 0 != 0 { + for pos := curr_block*SU; pos < n; pos += 1 { + a := (^byte)(x+pos)^ + if a ~ 0 != 0 { + return -1 if int(a) < 0 else +1 + } + } + } + } + + for /**/; offset < n; offset += 1 { + a := (^byte)(x+offset)^ + if a ~ 0 != 0 { + return -1 if int(a) < 0 else +1 + } + } + + return 0 +} + +string_eq :: proc "contextless" (lhs, rhs: string) -> bool { + x := transmute(Raw_String)lhs + y := transmute(Raw_String)rhs + if x.len != y.len { + return false + } + return #force_inline memory_equal(x.data, y.data, x.len) +} + +string_cmp :: proc "contextless" (a, b: string) -> int { + x := transmute(Raw_String)a + y := transmute(Raw_String)b + + ret := memory_compare(x.data, y.data, min(x.len, y.len)) + if ret == 0 && x.len != y.len { + return -1 if x.len < y.len else +1 + } + return ret +} + +string_ne :: #force_inline proc "contextless" (a, b: string) -> bool { return !string_eq(a, b) } +string_lt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) < 0 } +string_gt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) > 0 } +string_le :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) <= 0 } +string_ge :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) >= 0 } + +cstring_len :: proc "contextless" (s: cstring) -> int { + p0 := uintptr((^byte)(s)) + p := p0 + for p != 0 && (^byte)(p)^ != 0 { + p += 1 + } + return int(p - p0) +} + +cstring_to_string :: proc "contextless" (s: cstring) -> string { + if s == nil { + return "" + } + ptr := (^byte)(s) + n := cstring_len(s) + return transmute(string)Raw_String{ptr, n} +} + + +cstring_eq :: proc "contextless" (lhs, rhs: cstring) -> bool { + x := ([^]byte)(lhs) + y := ([^]byte)(rhs) + if x == y { + return true + } + if (x == nil) ~ (y == nil) { + return false + } + xn := cstring_len(lhs) + yn := cstring_len(rhs) + if xn != yn { + return false + } + return #force_inline memory_equal(x, y, xn) +} + +cstring_cmp :: proc "contextless" (lhs, rhs: cstring) -> int { + x := ([^]byte)(lhs) + y := ([^]byte)(rhs) + if x == y { + return 0 + } + if (x == nil) ~ (y == nil) { + return -1 if x == nil else +1 + } + xn := cstring_len(lhs) + yn := cstring_len(rhs) + ret := memory_compare(x, y, min(xn, yn)) + if ret == 0 && xn != yn { + return -1 if xn < yn else +1 + } + return ret +} + +cstring_ne :: #force_inline proc "contextless" (a, b: cstring) -> bool { return !cstring_eq(a, b) } +cstring_lt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) < 0 } +cstring_gt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) > 0 } +cstring_le :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) <= 0 } +cstring_ge :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) >= 0 } + + +complex32_eq :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) == real(b) && imag(a) == imag(b) } +complex32_ne :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) != real(b) || imag(a) != imag(b) } + +complex64_eq :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) == real(b) && imag(a) == imag(b) } +complex64_ne :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) != real(b) || imag(a) != imag(b) } + +complex128_eq :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) == real(b) && imag(a) == imag(b) } +complex128_ne :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) != real(b) || imag(a) != imag(b) } + + +quaternion64_eq :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } +quaternion64_ne :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } + +quaternion128_eq :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } +quaternion128_ne :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } + +quaternion256_eq :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } +quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } + + +string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) { + // NOTE(bill): Duplicated here to remove dependency on package unicode/utf8 + + @static accept_sizes := [256]u8{ + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7f + + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8f + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9f + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xa0-0xaf + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xb0-0xbf + 0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xc0-0xcf + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xd0-0xdf + 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xe0-0xef + 0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xf0-0xff + } + Accept_Range :: struct {lo, hi: u8} + + @static accept_ranges := [5]Accept_Range{ + {0x80, 0xbf}, + {0xa0, 0xbf}, + {0x80, 0x9f}, + {0x90, 0xbf}, + {0x80, 0x8f}, + } + + MASKX :: 0b0011_1111 + MASK2 :: 0b0001_1111 + MASK3 :: 0b0000_1111 + MASK4 :: 0b0000_0111 + + LOCB :: 0b1000_0000 + HICB :: 0b1011_1111 + + + RUNE_ERROR :: '\ufffd' + + n := len(s) + if n < 1 { + return RUNE_ERROR, 0 + } + s0 := s[0] + x := accept_sizes[s0] + if x >= 0xF0 { + mask := rune(x) << 31 >> 31 // NOTE(bill): Create 0x0000 or 0xffff. + return rune(s[0])&~mask | RUNE_ERROR&mask, 1 + } + sz := x & 7 + accept := accept_ranges[x>>4] + if n < int(sz) { + return RUNE_ERROR, 1 + } + b1 := s[1] + if b1 < accept.lo || accept.hi < b1 { + return RUNE_ERROR, 1 + } + if sz == 2 { + return rune(s0&MASK2)<<6 | rune(b1&MASKX), 2 + } + b2 := s[2] + if b2 < LOCB || HICB < b2 { + return RUNE_ERROR, 1 + } + if sz == 3 { + return rune(s0&MASK3)<<12 | rune(b1&MASKX)<<6 | rune(b2&MASKX), 3 + } + b3 := s[3] + if b3 < LOCB || HICB < b3 { + return RUNE_ERROR, 1 + } + return rune(s0&MASK4)<<18 | rune(b1&MASKX)<<12 | rune(b2&MASKX)<<6 | rune(b3&MASKX), 4 +} + +string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) { + RUNE_ERROR :: '\ufffd' + RUNE_SELF :: 0x80 + UTF_MAX :: 4 + + r: rune + size: int + start, end, limit: int + + end = len(s) + if end == 0 { + return RUNE_ERROR, 0 + } + start = end-1 + r = rune(s[start]) + if r < RUNE_SELF { + return r, 1 + } + + limit = max(end - UTF_MAX, 0) + + for start-=1; start >= limit; start-=1 { + if (s[start] & 0xc0) != RUNE_SELF { + break + } + } + + start = max(start, 0) + r, size = string_decode_rune(s[start:end]) + if start+size != end { + return RUNE_ERROR, 1 + } + return r, size +} + +abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * f16(intrinsics.sqrt(f32(1 + q*q))) +} +abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 { + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * intrinsics.sqrt(1 + q*q) +} +abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 { + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * intrinsics.sqrt(1 + q*q) +} +abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 { + r, i, j, k := real(x), imag(x), jmag(x), kmag(x) + return f16(intrinsics.sqrt(f32(r*r + i*i + j*j + k*k))) +} +abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 { + r, i, j, k := real(x), imag(x), jmag(x), kmag(x) + return intrinsics.sqrt(r*r + i*i + j*j + k*k) +} +abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 { + r, i, j, k := real(x), imag(x), jmag(x), kmag(x) + return intrinsics.sqrt(r*r + i*i + j*j + k*k) +} + + +quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 { + e, f: f16 + + if abs(real(m)) >= abs(imag(m)) { + ratio := imag(m) / real(m) + denom := real(m) + ratio*imag(m) + e = (real(n) + imag(n)*ratio) / denom + f = (imag(n) - real(n)*ratio) / denom + } else { + ratio := real(m) / imag(m) + denom := imag(m) + ratio*real(m) + e = (real(n)*ratio + imag(n)) / denom + f = (imag(n)*ratio - real(n)) / denom + } + + return complex(e, f) +} + + +quo_complex64 :: proc "contextless" (n, m: complex64) -> complex64 { + e, f: f32 + + if abs(real(m)) >= abs(imag(m)) { + ratio := imag(m) / real(m) + denom := real(m) + ratio*imag(m) + e = (real(n) + imag(n)*ratio) / denom + f = (imag(n) - real(n)*ratio) / denom + } else { + ratio := real(m) / imag(m) + denom := imag(m) + ratio*real(m) + e = (real(n)*ratio + imag(n)) / denom + f = (imag(n)*ratio - real(n)) / denom + } + + return complex(e, f) +} + +quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 { + e, f: f64 + + if abs(real(m)) >= abs(imag(m)) { + ratio := imag(m) / real(m) + denom := real(m) + ratio*imag(m) + e = (real(n) + imag(n)*ratio) / denom + f = (imag(n) - real(n)*ratio) / denom + } else { + ratio := real(m) / imag(m) + denom := imag(m) + ratio*real(m) + e = (real(n)*ratio + imag(n)) / denom + f = (imag(n)*ratio - real(n)) / denom + } + + return complex(e, f) +} + +mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 + t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 + t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 + t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 + t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 + t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 + t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 + t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 + t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 + t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) + + t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 + t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 + t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) + + t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 + t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 + t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) + + t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 + t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 + t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +@(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +truncsfhf2 :: proc "c" (value: f32) -> __float16 { + v: struct #raw_union { i: u32, f: f32 } + i, s, e, m: i32 + + v.f = value + i = i32(v.i) + + s = (i >> 16) & 0x00008000 + e = ((i >> 23) & 0x000000ff) - (127 - 15) + m = i & 0x007fffff + + + if e <= 0 { + if e < -10 { + return transmute(__float16)u16(s) + } + m = (m | 0x00800000) >> u32(1 - e) + + if m & 0x00001000 != 0 { + m += 0x00002000 + } + + return transmute(__float16)u16(s | (m >> 13)) + } else if e == 0xff - (127 - 15) { + if m == 0 { + return transmute(__float16)u16(s | 0x7c00) /* NOTE(bill): infinity */ + } else { + /* NOTE(bill): NAN */ + m >>= 13 + return transmute(__float16)u16(s | 0x7c00 | m | i32(m == 0)) + } + } else { + if m & 0x00001000 != 0 { + m += 0x00002000 + if (m & 0x00800000) != 0 { + m = 0 + e += 1 + } + } + + if e > 30 { + f := i64(1e12) + for j := 0; j < 10; j += 1 { + /* NOTE(bill): Cause overflow */ + g := intrinsics.volatile_load(&f) + g *= g + intrinsics.volatile_store(&f, g) + } + + return transmute(__float16)u16(s | 0x7c00) + } + + return transmute(__float16)u16(s | (e << 10) | (m >> 13)) + } +} + + +@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +truncdfhf2 :: proc "c" (value: f64) -> __float16 { + return truncsfhf2(f32(value)) +} + +@(link_name="__gnu_h2f_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +gnu_h2f_ieee :: proc "c" (value_: __float16) -> f32 { + fp32 :: struct #raw_union { u: u32, f: f32 } + + value := transmute(u16)value_ + v: fp32 + magic, inf_or_nan: fp32 + magic.u = u32((254 - 15) << 23) + inf_or_nan.u = u32((127 + 16) << 23) + + v.u = u32(value & 0x7fff) << 13 + v.f *= magic.f + if v.f >= inf_or_nan.f { + v.u |= 255 << 23 + } + v.u |= u32(value & 0x8000) << 16 + return v.f +} + + +@(link_name="__gnu_f2h_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +gnu_f2h_ieee :: proc "c" (value: f32) -> __float16 { + return truncsfhf2(value) +} + +@(link_name="__extendhfsf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +extendhfsf2 :: proc "c" (value: __float16) -> f32 { + return gnu_h2f_ieee(value) +} + + + +@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +floattidf :: proc "c" (a: i128) -> f64 { +when IS_WASM { + return 0 +} else { + DBL_MANT_DIG :: 53 + if a == 0 { + return 0.0 + } + a := a + N :: size_of(i128) * 8 + s := a >> (N-1) + a = (a ~ s) - s + sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits + e := i32(sd - 1) // exponent + if sd > DBL_MANT_DIG { + switch sd { + case DBL_MANT_DIG + 1: + a <<= 1 + case DBL_MANT_DIG + 2: + // okay + case: + a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | + i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) + } + + a |= i128((a & 4) != 0) + a += 1 + a >>= 2 + + if a & (i128(1) << DBL_MANT_DIG) != 0 { + a >>= 1 + e += 1 + } + } else { + a <<= u128(DBL_MANT_DIG - sd) & 127 + } + fb: [2]u32 + fb[1] = (u32(s) & 0x80000000) | // sign + (u32(e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high + fb[0] = u32(a) // mantissa-low + return transmute(f64)fb +} +} + + +@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +floattidf_unsigned :: proc "c" (a: u128) -> f64 { +when IS_WASM { + return 0 +} else { + DBL_MANT_DIG :: 53 + if a == 0 { + return 0.0 + } + a := a + N :: size_of(u128) * 8 + sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits + e := i32(sd - 1) // exponent + if sd > DBL_MANT_DIG { + switch sd { + case DBL_MANT_DIG + 1: + a <<= 1 + case DBL_MANT_DIG + 2: + // okay + case: + a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | + u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) + } + + a |= u128((a & 4) != 0) + a += 1 + a >>= 2 + + if a & (1 << DBL_MANT_DIG) != 0 { + a >>= 1 + e += 1 + } + } else { + a <<= u128(DBL_MANT_DIG - sd) + } + fb: [2]u32 + fb[1] = (0) | // sign + u32((e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high + fb[0] = u32(a) // mantissa-low + return transmute(f64)fb +} +} + + + +@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 { + // TODO(bill): implement `fixunsdfti` correctly + x := u64(a) + return u128(x) +} + +@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 { + // TODO(bill): implement `fixunsdfdi` correctly + x := i64(a) + return i128(x) +} + + + + +@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +umodti3 :: proc "c" (a, b: u128) -> u128 { + r: u128 = --- + _ = udivmod128(a, b, &r) + return r +} + + +@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { + return udivmod128(a, b, rem) +} + +@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +udivti3 :: proc "c" (a, b: u128) -> u128 { + return udivmodti4(a, b, nil) +} + + +@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +modti3 :: proc "c" (a, b: i128) -> i128 { + s_a := a >> (128 - 1) + s_b := b >> (128 - 1) + an := (a ~ s_a) - s_a + bn := (b ~ s_b) - s_b + + r: u128 = --- + _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) + return (transmute(i128)r ~ s_a) - s_a +} + + +@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { + u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) + return transmute(i128)u +} + +@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +divti3 :: proc "c" (a, b: i128) -> i128 { + u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) + return transmute(i128)u +} + + +@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixdfti :: proc(a: u64) -> i128 { + significandBits :: 52 + typeWidth :: (size_of(u64)*8) + exponentBits :: (typeWidth - significandBits - 1) + maxExponent :: ((1 << exponentBits) - 1) + exponentBias :: (maxExponent >> 1) + + implicitBit :: (u64(1) << significandBits) + significandMask :: (implicitBit - 1) + signBit :: (u64(1) << (significandBits + exponentBits)) + absMask :: (signBit - 1) + exponentMask :: (absMask ~ significandMask) + + // Break a into sign, exponent, significand + aRep := a + aAbs := aRep & absMask + sign := i128(-1 if aRep & signBit != 0 else 1) + exponent := u64((aAbs >> significandBits) - exponentBias) + significand := u64((aAbs & significandMask) | implicitBit) + + // If exponent is negative, the result is zero. + if exponent < 0 { + return 0 + } + + // If the value is too large for the integer type, saturate. + if exponent >= size_of(i128) * 8 { + return max(i128) if sign == 1 else min(i128) + } + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if exponent < significandBits { + return sign * i128(significand >> (significandBits - exponent)) + } else { + return sign * (i128(significand) << (exponent - significandBits)) + } + +} diff --git a/base/runtime/os_specific.odin b/base/runtime/os_specific.odin new file mode 100644 index 000000000..022d315d4 --- /dev/null +++ b/base/runtime/os_specific.odin @@ -0,0 +1,7 @@ +package runtime + +_OS_Errno :: distinct int + +os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + return _os_write(data) +} diff --git a/base/runtime/os_specific_any.odin b/base/runtime/os_specific_any.odin new file mode 100644 index 000000000..6a96655c4 --- /dev/null +++ b/base/runtime/os_specific_any.odin @@ -0,0 +1,16 @@ +//+build !darwin +//+build !freestanding +//+build !js +//+build !wasi +//+build !windows +package runtime + +import "core:os" + +// TODO(bill): reimplement `os.write` so that it does not rely on package os +// NOTE: Use os_specific_linux.odin, os_specific_darwin.odin, etc +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + context = default_context() + n, err := os.write(os.stderr, data) + return int(n), _OS_Errno(err) +} diff --git a/base/runtime/os_specific_darwin.odin b/base/runtime/os_specific_darwin.odin new file mode 100644 index 000000000..5de9a7d57 --- /dev/null +++ b/base/runtime/os_specific_darwin.odin @@ -0,0 +1,12 @@ +//+build darwin +package runtime + +import "core:intrinsics" + +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + ret := intrinsics.syscall(0x2000004, 1, uintptr(raw_data(data)), uintptr(len(data))) + if ret < 0 { + return 0, _OS_Errno(-ret) + } + return int(ret), 0 +} diff --git a/base/runtime/os_specific_freestanding.odin b/base/runtime/os_specific_freestanding.odin new file mode 100644 index 000000000..a6d04cefb --- /dev/null +++ b/base/runtime/os_specific_freestanding.odin @@ -0,0 +1,7 @@ +//+build freestanding +package runtime + +// TODO(bill): reimplement `os.write` +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + return 0, -1 +} diff --git a/base/runtime/os_specific_js.odin b/base/runtime/os_specific_js.odin new file mode 100644 index 000000000..246141d87 --- /dev/null +++ b/base/runtime/os_specific_js.odin @@ -0,0 +1,12 @@ +//+build js +package runtime + +foreign import "odin_env" + +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + foreign odin_env { + write :: proc "contextless" (fd: u32, p: []byte) --- + } + write(1, data) + return len(data), 0 +} diff --git a/base/runtime/os_specific_wasi.odin b/base/runtime/os_specific_wasi.odin new file mode 100644 index 000000000..3f69504ee --- /dev/null +++ b/base/runtime/os_specific_wasi.odin @@ -0,0 +1,10 @@ +//+build wasi +package runtime + +import "core:sys/wasm/wasi" + +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + data := (wasi.ciovec_t)(data) + n, err := wasi.fd_write(1, {data}) + return int(n), _OS_Errno(err) +} diff --git a/base/runtime/os_specific_windows.odin b/base/runtime/os_specific_windows.odin new file mode 100644 index 000000000..4a5907466 --- /dev/null +++ b/base/runtime/os_specific_windows.odin @@ -0,0 +1,135 @@ +//+build windows +package runtime + +foreign import kernel32 "system:Kernel32.lib" + +@(private="file") +@(default_calling_convention="system") +foreign kernel32 { + // NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency + + // os_write + GetStdHandle :: proc(which: u32) -> rawptr --- + SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 --- + WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 --- + GetLastError :: proc() -> u32 --- + + // default_allocator + GetProcessHeap :: proc() -> rawptr --- + HeapAlloc :: proc(hHeap: rawptr, dwFlags: u32, dwBytes: uint) -> rawptr --- + HeapReAlloc :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr, dwBytes: uint) -> rawptr --- + HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 --- +} + +_os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check { + if len(data) == 0 { + return 0, 0 + } + + STD_ERROR_HANDLE :: ~u32(0) -12 + 1 + HANDLE_FLAG_INHERIT :: 0x00000001 + MAX_RW :: 1<<30 + + h := GetStdHandle(STD_ERROR_HANDLE) + when size_of(uintptr) == 8 { + SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0) + } + + single_write_length: u32 + total_write: i64 + length := i64(len(data)) + + for total_write < length { + remaining := length - total_write + to_write := u32(min(i32(remaining), MAX_RW)) + + e := WriteFile(h, &data[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + err = _OS_Errno(GetLastError()) + n = int(total_write) + return + } + total_write += i64(single_write_length) + } + n = int(total_write) + return +} + +heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + HEAP_ZERO_MEMORY :: 0x00000008 + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) +} +heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { + if new_size == 0 { + heap_free(ptr) + return nil + } + if ptr == nil { + return heap_alloc(new_size) + } + + HEAP_ZERO_MEMORY :: 0x00000008 + return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) +} +heap_free :: proc "contextless" (ptr: rawptr) { + if ptr == nil { + return + } + HeapFree(GetProcessHeap(), 0, ptr) +} + + +// +// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. +// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert +// padding. We also store the original pointer returned by heap_alloc right before +// the pointer we return to the user. +// + + + +_windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, Allocator_Error) { + if size == 0 { + _windows_default_free(old_ptr) + return nil, nil + } + + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := ([^]rawptr)(old_ptr)[-1] + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) + } else { + allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) + } + aligned_mem := ([^]u8)(allocated_mem)[size_of(rawptr):] + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space || allocated_mem == nil { + return nil, .Out_Of_Memory + } + + aligned_mem = ([^]byte)(aligned_ptr) + ([^]rawptr)(aligned_mem)[-1] = allocated_mem + + return aligned_mem[:size], nil +} + +_windows_default_alloc :: proc "contextless" (size, alignment: int, zero_memory := true) -> ([]byte, Allocator_Error) { + return _windows_default_alloc_or_resize(size, alignment, nil, zero_memory) +} + + +_windows_default_free :: proc "contextless" (ptr: rawptr) { + if ptr != nil { + heap_free(([^]rawptr)(ptr)[-1]) + } +} + +_windows_default_resize :: proc "contextless" (p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, Allocator_Error) { + return _windows_default_alloc_or_resize(new_size, new_alignment, p) +} diff --git a/base/runtime/print.odin b/base/runtime/print.odin new file mode 100644 index 000000000..87c8757d5 --- /dev/null +++ b/base/runtime/print.odin @@ -0,0 +1,489 @@ +package runtime + +_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz" + +@(private="file") +_INTEGER_DIGITS_VAR := _INTEGER_DIGITS + +when !ODIN_NO_RTTI { + print_any_single :: proc "contextless" (arg: any) { + x := arg + if x.data == nil { + print_string("nil") + return + } + + if loc, ok := x.(Source_Code_Location); ok { + print_caller_location(loc) + return + } + x.id = typeid_base(x.id) + switch v in x { + case typeid: print_typeid(v) + case ^Type_Info: print_type(v) + + case string: print_string(v) + case cstring: print_string(string(v)) + case []byte: print_string(string(v)) + + case rune: print_rune(v) + + case u8: print_u64(u64(v)) + case u16: print_u64(u64(v)) + case u16le: print_u64(u64(v)) + case u16be: print_u64(u64(v)) + case u32: print_u64(u64(v)) + case u32le: print_u64(u64(v)) + case u32be: print_u64(u64(v)) + case u64: print_u64(u64(v)) + case u64le: print_u64(u64(v)) + case u64be: print_u64(u64(v)) + + case i8: print_i64(i64(v)) + case i16: print_i64(i64(v)) + case i16le: print_i64(i64(v)) + case i16be: print_i64(i64(v)) + case i32: print_i64(i64(v)) + case i32le: print_i64(i64(v)) + case i32be: print_i64(i64(v)) + case i64: print_i64(i64(v)) + case i64le: print_i64(i64(v)) + case i64be: print_i64(i64(v)) + + case int: print_int(v) + case uint: print_uint(v) + case uintptr: print_uintptr(v) + case rawptr: print_uintptr(uintptr(v)) + + case bool: print_string("true" if v else "false") + case b8: print_string("true" if v else "false") + case b16: print_string("true" if v else "false") + case b32: print_string("true" if v else "false") + case b64: print_string("true" if v else "false") + + case: + ti := type_info_of(x.id) + #partial switch v in ti.variant { + case Type_Info_Pointer, Type_Info_Multi_Pointer: + print_uintptr((^uintptr)(x.data)^) + return + } + + print_string("") + } + } + println_any :: proc "contextless" (args: ..any) { + context = default_context() + loop: for arg, i in args { + assert(arg.id != nil) + if i != 0 { + print_string(" ") + } + print_any_single(arg) + } + print_string("\n") + } +} + + +encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) { + r := c + + buf: [4]u8 + i := u32(r) + mask :: u8(0x3f) + if i <= 1<<7-1 { + buf[0] = u8(r) + return buf, 1 + } + if i <= 1<<11-1 { + buf[0] = 0xc0 | u8(r>>6) + buf[1] = 0x80 | u8(r) & mask + return buf, 2 + } + + // Invalid or Surrogate range + if i > 0x0010ffff || + (0xd800 <= i && i <= 0xdfff) { + r = 0xfffd + } + + if i <= 1<<16-1 { + buf[0] = 0xe0 | u8(r>>12) + buf[1] = 0x80 | u8(r>>6) & mask + buf[2] = 0x80 | u8(r) & mask + return buf, 3 + } + + buf[0] = 0xf0 | u8(r>>18) + buf[1] = 0x80 | u8(r>>12) & mask + buf[2] = 0x80 | u8(r>>6) & mask + buf[3] = 0x80 | u8(r) & mask + return buf, 4 +} + +print_string :: proc "contextless" (str: string) -> (n: int) { + n, _ = os_write(transmute([]byte)str) + return +} + +print_strings :: proc "contextless" (args: ..string) -> (n: int) { + for str in args { + m, err := os_write(transmute([]byte)str) + n += m + if err != 0 { + break + } + } + return +} + +print_byte :: proc "contextless" (b: byte) -> (n: int) { + n, _ = os_write([]byte{b}) + return +} + +print_encoded_rune :: proc "contextless" (r: rune) { + print_byte('\'') + + switch r { + case '\a': print_string("\\a") + case '\b': print_string("\\b") + case '\e': print_string("\\e") + case '\f': print_string("\\f") + case '\n': print_string("\\n") + case '\r': print_string("\\r") + case '\t': print_string("\\t") + case '\v': print_string("\\v") + case: + if r <= 0 { + print_string("\\x00") + } else if r < 32 { + n0, n1 := u8(r) >> 4, u8(r) & 0xf + print_string("\\x") + print_byte(_INTEGER_DIGITS_VAR[n0]) + print_byte(_INTEGER_DIGITS_VAR[n1]) + } else { + print_rune(r) + } + } + print_byte('\'') +} + +print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { + RUNE_SELF :: 0x80 + + if r < RUNE_SELF { + return print_byte(byte(r)) + } + + b, n := encode_rune(r) + m, _ := os_write(b[:n]) + return m +} + + +print_u64 :: proc "contextless" (x: u64) #no_bounds_check { + a: [129]byte + i := len(a) + b := u64(10) + u := x + for u >= b { + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + u /= b + } + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + + os_write(a[i:]) +} + + +print_i64 :: proc "contextless" (x: i64) #no_bounds_check { + b :: i64(10) + + u := x + neg := u < 0 + u = abs(u) + + a: [129]byte + i := len(a) + for u >= b { + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + u /= b + } + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + if neg { + i -= 1; a[i] = '-' + } + + os_write(a[i:]) +} + +print_uint :: proc "contextless" (x: uint) { print_u64(u64(x)) } +print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) } +print_int :: proc "contextless" (x: int) { print_i64(i64(x)) } + +print_caller_location :: proc "contextless" (loc: Source_Code_Location) { + print_string(loc.file_path) + when ODIN_ERROR_POS_STYLE == .Default { + print_byte('(') + print_u64(u64(loc.line)) + print_byte(':') + print_u64(u64(loc.column)) + print_byte(')') + } else when ODIN_ERROR_POS_STYLE == .Unix { + print_byte(':') + print_u64(u64(loc.line)) + print_byte(':') + print_u64(u64(loc.column)) + print_byte(':') + } else { + #panic("unhandled ODIN_ERROR_POS_STYLE") + } +} +print_typeid :: proc "contextless" (id: typeid) { + when ODIN_NO_RTTI { + if id == nil { + print_string("nil") + } else { + print_string("") + } + } else { + if id == nil { + print_string("nil") + } else { + ti := type_info_of(id) + print_type(ti) + } + } +} +print_type :: proc "contextless" (ti: ^Type_Info) { + if ti == nil { + print_string("nil") + return + } + + switch info in ti.variant { + case Type_Info_Named: + print_string(info.name) + case Type_Info_Integer: + switch ti.id { + case int: print_string("int") + case uint: print_string("uint") + case uintptr: print_string("uintptr") + case: + print_byte('i' if info.signed else 'u') + print_u64(u64(8*ti.size)) + } + case Type_Info_Rune: + print_string("rune") + case Type_Info_Float: + print_byte('f') + print_u64(u64(8*ti.size)) + case Type_Info_Complex: + print_string("complex") + print_u64(u64(8*ti.size)) + case Type_Info_Quaternion: + print_string("quaternion") + print_u64(u64(8*ti.size)) + case Type_Info_String: + print_string("string") + case Type_Info_Boolean: + switch ti.id { + case bool: print_string("bool") + case: + print_byte('b') + print_u64(u64(8*ti.size)) + } + case Type_Info_Any: + print_string("any") + case Type_Info_Type_Id: + print_string("typeid") + + case Type_Info_Pointer: + if info.elem == nil { + print_string("rawptr") + } else { + print_string("^") + print_type(info.elem) + } + case Type_Info_Multi_Pointer: + print_string("[^]") + print_type(info.elem) + case Type_Info_Soa_Pointer: + print_string("#soa ^") + print_type(info.elem) + case Type_Info_Procedure: + print_string("proc") + if info.params == nil { + print_string("()") + } else { + t := info.params.variant.(Type_Info_Parameters) + print_byte('(') + for t, i in t.types { + if i > 0 { print_string(", ") } + print_type(t) + } + print_string(")") + } + if info.results != nil { + print_string(" -> ") + print_type(info.results) + } + case Type_Info_Parameters: + count := len(info.names) + if count != 1 { print_byte('(') } + for name, i in info.names { + if i > 0 { print_string(", ") } + + t := info.types[i] + + if len(name) > 0 { + print_string(name) + print_string(": ") + } + print_type(t) + } + if count != 1 { print_string(")") } + + case Type_Info_Array: + print_byte('[') + print_u64(u64(info.count)) + print_byte(']') + print_type(info.elem) + + case Type_Info_Enumerated_Array: + if info.is_sparse { + print_string("#sparse") + } + print_byte('[') + print_type(info.index) + print_byte(']') + print_type(info.elem) + + + case Type_Info_Dynamic_Array: + print_string("[dynamic]") + print_type(info.elem) + case Type_Info_Slice: + print_string("[]") + print_type(info.elem) + + case Type_Info_Map: + print_string("map[") + print_type(info.key) + print_byte(']') + print_type(info.value) + + case Type_Info_Struct: + switch info.soa_kind { + case .None: // Ignore + case .Fixed: + print_string("#soa[") + print_u64(u64(info.soa_len)) + print_byte(']') + print_type(info.soa_base_type) + return + case .Slice: + print_string("#soa[]") + print_type(info.soa_base_type) + return + case .Dynamic: + print_string("#soa[dynamic]") + print_type(info.soa_base_type) + return + } + + print_string("struct ") + if info.is_packed { print_string("#packed ") } + if info.is_raw_union { print_string("#raw_union ") } + if info.custom_align { + print_string("#align(") + print_u64(u64(ti.align)) + print_string(") ") + } + print_byte('{') + for name, i in info.names { + if i > 0 { print_string(", ") } + print_string(name) + print_string(": ") + print_type(info.types[i]) + } + print_byte('}') + + case Type_Info_Union: + print_string("union ") + if info.custom_align { + print_string("#align(") + print_u64(u64(ti.align)) + print_string(") ") + } + if info.no_nil { + print_string("#no_nil ") + } + print_byte('{') + for variant, i in info.variants { + if i > 0 { print_string(", ") } + print_type(variant) + } + print_string("}") + + case Type_Info_Enum: + print_string("enum ") + print_type(info.base) + print_string(" {") + for name, i in info.names { + if i > 0 { print_string(", ") } + print_string(name) + } + print_string("}") + + case Type_Info_Bit_Set: + print_string("bit_set[") + + #partial switch elem in type_info_base(info.elem).variant { + case Type_Info_Enum: + print_type(info.elem) + case Type_Info_Rune: + print_encoded_rune(rune(info.lower)) + print_string("..") + print_encoded_rune(rune(info.upper)) + case: + print_i64(info.lower) + print_string("..") + print_i64(info.upper) + } + if info.underlying != nil { + print_string("; ") + print_type(info.underlying) + } + print_byte(']') + + + case Type_Info_Simd_Vector: + print_string("#simd[") + print_u64(u64(info.count)) + print_byte(']') + print_type(info.elem) + + case Type_Info_Relative_Pointer: + print_string("#relative(") + print_type(info.base_integer) + print_string(") ") + print_type(info.pointer) + + case Type_Info_Relative_Multi_Pointer: + print_string("#relative(") + print_type(info.base_integer) + print_string(") ") + print_type(info.pointer) + + case Type_Info_Matrix: + print_string("matrix[") + print_u64(u64(info.row_count)) + print_string(", ") + print_u64(u64(info.column_count)) + print_string("]") + print_type(info.elem) + } +} diff --git a/base/runtime/procs.odin b/base/runtime/procs.odin new file mode 100644 index 000000000..454574c35 --- /dev/null +++ b/base/runtime/procs.odin @@ -0,0 +1,95 @@ +package runtime + +when ODIN_NO_CRT && ODIN_OS == .Windows { + foreign import lib "system:NtDll.lib" + + @(private="file") + @(default_calling_convention="system") + foreign lib { + RtlMoveMemory :: proc(dst, s: rawptr, length: int) --- + RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- + } + + @(link_name="memset", linkage="strong", require) + memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + RtlFillMemory(ptr, len, val) + return ptr + } + @(link_name="memmove", linkage="strong", require) + memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + RtlMoveMemory(dst, src, len) + return dst + } + @(link_name="memcpy", linkage="strong", require) + memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + RtlMoveMemory(dst, src, len) + return dst + } +} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { + @(link_name="memset", linkage="strong", require) + memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + if ptr != nil && len != 0 { + b := byte(val) + p := ([^]byte)(ptr) + for i := 0; i < len; i += 1 { + p[i] = b + } + } + return ptr + } + + @(link_name="bzero", linkage="strong", require) + bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr { + if ptr != nil && len != 0 { + p := ([^]byte)(ptr) + for i := 0; i < len; i += 1 { + p[i] = 0 + } + } + return ptr + } + + @(link_name="memmove", linkage="strong", require) + memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + d, s := ([^]byte)(dst), ([^]byte)(src) + if d == s || len == 0 { + return dst + } + if d > s && uintptr(d)-uintptr(s) < uintptr(len) { + for i := len-1; i >= 0; i -= 1 { + d[i] = s[i] + } + return dst + } + + if s > d && uintptr(s)-uintptr(d) < uintptr(len) { + for i := 0; i < len; i += 1 { + d[i] = s[i] + } + return dst + } + return memcpy(dst, src, len) + } + @(link_name="memcpy", linkage="strong", require) + memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + d, s := ([^]byte)(dst), ([^]byte)(src) + if d != s { + for i := 0; i < len; i += 1 { + d[i] = s[i] + } + } + return d + + } +} else { + memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + if ptr != nil && len != 0 { + b := byte(val) + p := ([^]byte)(ptr) + for i := 0; i < len; i += 1 { + p[i] = b + } + } + return ptr + } +} \ No newline at end of file diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin new file mode 100644 index 000000000..9c53b5b16 --- /dev/null +++ b/base/runtime/procs_darwin.odin @@ -0,0 +1,21 @@ +//+private +package runtime + +foreign import "system:Foundation.framework" + +import "core:intrinsics" + +objc_id :: ^intrinsics.objc_object +objc_Class :: ^intrinsics.objc_class +objc_SEL :: ^intrinsics.objc_selector + +foreign Foundation { + objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- + sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- + objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- + + objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- + objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- + objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- +} diff --git a/base/runtime/procs_js.odin b/base/runtime/procs_js.odin new file mode 100644 index 000000000..d3e12410c --- /dev/null +++ b/base/runtime/procs_js.odin @@ -0,0 +1,15 @@ +//+build js +package runtime + +init_default_context_for_js: Context +@(init, private="file") +init_default_context :: proc() { + init_default_context_for_js = context +} + +@(export) +@(link_name="default_context_ptr") +default_context_ptr :: proc "contextless" () -> ^Context { + return &init_default_context_for_js +} + diff --git a/base/runtime/procs_wasm.odin b/base/runtime/procs_wasm.odin new file mode 100644 index 000000000..26dcfef77 --- /dev/null +++ b/base/runtime/procs_wasm.odin @@ -0,0 +1,40 @@ +//+build wasm32, wasm64p32 +package runtime + +@(private="file") +ti_int :: struct #raw_union { + using s: struct { lo, hi: u64 }, + all: i128, +} + +@(link_name="__ashlti3", linkage="strong") +__ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 { + bits_in_dword :: size_of(u32)*8 + b := u32(b_) + + input, result: ti_int + input.all = a + if b & bits_in_dword != 0 { + result.lo = 0 + result.hi = input.lo << (b-bits_in_dword) + } else { + if b == 0 { + return a + } + result.lo = input.lo<>(bits_in_dword-b)) + } + return result.all +} + + +@(link_name="__multi3", linkage="strong") +__multi3 :: proc "contextless" (a, b: i128) -> i128 { + x, y, r: ti_int + + x.all = a + y.all = b + r.all = i128(x.lo * y.lo) // TODO this is incorrect + r.hi += x.hi*y.lo + x.lo*y.hi + return r.all +} \ No newline at end of file diff --git a/base/runtime/procs_windows_amd64.asm b/base/runtime/procs_windows_amd64.asm new file mode 100644 index 000000000..f588b3453 --- /dev/null +++ b/base/runtime/procs_windows_amd64.asm @@ -0,0 +1,79 @@ +bits 64 + +global __chkstk +global _tls_index +global _fltused + +section .data + _tls_index: dd 0 + _fltused: dd 0x9875 + +section .text +; NOTE(flysand): The function call to __chkstk is called +; by the compiler, when we're allocating arrays larger than +; a page size. The reason is because the OS doesn't map the +; whole stack into memory all at once, but does so page-by-page. +; When the next page is touched, the CPU generates a page fault, +; which *the OS* is handling by allocating the next page in the +; stack until we reach the limit of stack size. +; +; This page is called the guard page, touching it will extend +; the size of the stack and overwrite the stack limit in the TEB. +; +; If we allocate a large enough array and start writing from the +; bottom of it, it's possible that we may start touching +; non-contiguous pages which are unmapped. OS only maps the stack +; page into the memory if the page above it was also mapped. +; +; Therefore the compilers insert this routine, the sole purpose +; of which is to step through the stack starting from the RSP +; down to the new RSP after allocation, and touch every page +; of the new allocation so that the stack is fully mapped for +; the new allocation +; +; I've gotten this code by disassembling the output of MSVC long +; time ago. I don't remember if I've cleaned it up, but it definately +; stinks. +; +; Additional notes: +; RAX (passed as parameter) holds the allocation's size +; GS:[0x10] references the current stack limit +; (i.e. bottom of the stack (i.e. lowest address accessible)) +; +; Also this stuff is windows-only kind of thing, because linux people +; didn't think stack that grows is cool enough for them, but the kernel +; totally supports this kind of stack. +__chkstk: + ;; Allocate 16 bytes to store values of r10 and r11 + sub rsp, 0x10 + mov [rsp], r10 + mov [rsp+0x8], r11 + ;; Set r10 to point to the stack as of the moment of the function call + lea r10, [rsp+0x18] + ;; Subtract r10 til the bottom of the stack allocation, if we overflow + ;; reset r10 to 0, we'll crash with segfault anyway + xor r11, r11 + sub r10, rax + cmovb r10, r11 + ;; Load r11 with the bottom of the stack (lowest allocated address) + mov r11, gs:[0x10] ; NOTE(flysand): gs:[0x10] is stack limit + ;; If the bottom of the allocation is above the bottom of the stack, + ;; we don't need to probe + cmp r10, r11 + jnb .end + ;; Align the bottom of the allocation down to page size + and r10w, 0xf000 +.loop: + ;; Move the pointer to the next guard page, and touch it by loading 0 + ;; into that page + lea r11, [r11-0x1000] + mov byte [r11], 0x0 + ;; Did we reach the bottom of the allocation? + cmp r10, r11 + jnz .loop +.end: + ;; Restore previous r10 and r11 and return + mov r10, [rsp] + mov r11, [rsp+0x8] + add rsp, 0x10 + ret \ No newline at end of file diff --git a/base/runtime/procs_windows_amd64.odin b/base/runtime/procs_windows_amd64.odin new file mode 100644 index 000000000..ea495f5fa --- /dev/null +++ b/base/runtime/procs_windows_amd64.odin @@ -0,0 +1,26 @@ +//+private +//+no-instrumentation +package runtime + +foreign import kernel32 "system:Kernel32.lib" + +@(private) +foreign kernel32 { + RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! --- +} + +windows_trap_array_bounds :: proc "contextless" () -> ! { + EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C + + + RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) +} + +windows_trap_type_assertion :: proc "contextless" () -> ! { + windows_trap_array_bounds() +} + +when ODIN_NO_CRT { + @(require) + foreign import crt_lib "procs_windows_amd64.asm" +} diff --git a/base/runtime/procs_windows_i386.odin b/base/runtime/procs_windows_i386.odin new file mode 100644 index 000000000..10422cf07 --- /dev/null +++ b/base/runtime/procs_windows_i386.odin @@ -0,0 +1,29 @@ +//+private +//+no-instrumentation +package runtime + +@require foreign import "system:int64.lib" + +foreign import kernel32 "system:Kernel32.lib" + +windows_trap_array_bounds :: proc "contextless" () -> ! { + DWORD :: u32 + ULONG_PTR :: uint + + EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C + + foreign kernel32 { + RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- + } + + RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) +} + +windows_trap_type_assertion :: proc "contextless" () -> ! { + windows_trap_array_bounds() +} + +@(private, export, link_name="_fltused") _fltused: i32 = 0x9875 + +@(private, export, link_name="_tls_index") _tls_index: u32 +@(private, export, link_name="_tls_array") _tls_array: u32 diff --git a/base/runtime/udivmod128.odin b/base/runtime/udivmod128.odin new file mode 100644 index 000000000..87ef73c2c --- /dev/null +++ b/base/runtime/udivmod128.odin @@ -0,0 +1,156 @@ +package runtime + +import "core:intrinsics" + +udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { + _ctz :: intrinsics.count_trailing_zeros + _clz :: intrinsics.count_leading_zeros + + n := transmute([2]u64)a + d := transmute([2]u64)b + q, r: [2]u64 + sr: u32 = 0 + + low :: 1 when ODIN_ENDIAN == .Big else 0 + high :: 1 - low + U64_BITS :: 8*size_of(u64) + U128_BITS :: 8*size_of(u128) + + // Special Cases + + if n[high] == 0 { + if d[high] == 0 { + if rem != nil { + res := n[low] % d[low] + rem^ = u128(res) + } + return u128(n[low] / d[low]) + } + + if rem != nil { + rem^ = u128(n[low]) + } + return 0 + } + + if d[low] == 0 { + if d[high] == 0 { + if rem != nil { + rem^ = u128(n[high] % d[low]) + } + return u128(n[high] / d[low]) + } + if n[low] == 0 { + if rem != nil { + r[high] = n[high] % d[high] + r[low] = 0 + rem^ = transmute(u128)r + } + return u128(n[high] / d[high]) + } + + if d[high] & (d[high]-1) == 0 { + if rem != nil { + r[low] = n[low] + r[high] = n[high] & (d[high] - 1) + rem^ = transmute(u128)r + } + return u128(n[high] >> _ctz(d[high])) + } + + sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) + if sr > U64_BITS - 2 { + if rem != nil { + rem^ = a + } + return 0 + } + + sr += 1 + + q[low] = 0 + q[high] = n[low] << u64(U64_BITS - sr) + r[high] = n[high] >> sr + r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) + } else { + if d[high] == 0 { + if d[low] & (d[low] - 1) == 0 { + if rem != nil { + rem^ = u128(n[low] & (d[low] - 1)) + } + if d[low] == 1 { + return a + } + sr = u32(_ctz(d[low])) + q[high] = n[high] >> sr + q[low] = (n[high] << (U64_BITS-sr)) | (n[low] >> sr) + return transmute(u128)q + } + + sr = 1 + U64_BITS + u32(_clz(d[low])) - u32(_clz(n[high])) + + switch { + case sr == U64_BITS: + q[low] = 0 + q[high] = n[low] + r[high] = 0 + r[low] = n[high] + case sr < U64_BITS: + q[low] = 0 + q[high] = n[low] << (U64_BITS - sr) + r[high] = n[high] >> sr + r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) + case: + q[low] = n[low] << (U128_BITS - sr) + q[high] = (n[high] << (U128_BITS - sr)) | (n[low] >> (sr - U64_BITS)) + r[high] = 0 + r[low] = n[high] >> (sr - U64_BITS) + } + } else { + sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) + + if sr > U64_BITS - 1 { + if rem != nil { + rem^ = a + } + return 0 + } + + sr += 1 + + q[low] = 0 + if sr == U64_BITS { + q[high] = n[low] + r[high] = 0 + r[low] = n[high] + } else { + r[high] = n[high] >> sr + r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) + q[high] = n[low] << (U64_BITS - sr) + } + } + } + + carry: u32 = 0 + r_all: u128 + + for ; sr > 0; sr -= 1 { + r[high] = (r[high] << 1) | (r[low] >> (U64_BITS - 1)) + r[low] = (r[low] << 1) | (q[high] >> (U64_BITS - 1)) + q[high] = (q[high] << 1) | (q[low] >> (U64_BITS - 1)) + q[low] = (q[low] << 1) | u64(carry) + + r_all = transmute(u128)r + s := i128(b - r_all - 1) >> (U128_BITS - 1) + carry = u32(s & 1) + r_all -= b & transmute(u128)s + r = transmute([2]u64)r_all + } + + q_all := ((transmute(u128)q) << 1) | u128(carry) + if rem != nil { + rem^ = r_all + } + + return q_all +} diff --git a/core/runtime/core.odin b/core/runtime/core.odin deleted file mode 100644 index 740482493..000000000 --- a/core/runtime/core.odin +++ /dev/null @@ -1,681 +0,0 @@ -// 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 -// -// 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 -// -//+no-instrumentation -package runtime - -import "core:intrinsics" - -// NOTE(bill): This must match the compiler's -Calling_Convention :: enum u8 { - Invalid = 0, - Odin = 1, - Contextless = 2, - CDecl = 3, - Std_Call = 4, - Fast_Call = 5, - - None = 6, - Naked = 7, - - _ = 8, // reserved - - Win64 = 9, - SysV = 10, -} - -Type_Info_Enum_Value :: distinct i64 - -Platform_Endianness :: enum u8 { - Platform = 0, - Little = 1, - Big = 2, -} - -// Procedure type to test whether two values of the same type are equal -Equal_Proc :: distinct proc "contextless" (rawptr, rawptr) -> bool -// Procedure type to hash a value, default seed value is 0 -Hasher_Proc :: distinct proc "contextless" (data: rawptr, seed: uintptr = 0) -> uintptr - -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, - pkg: string, - loc: Source_Code_Location, -} -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_Multi_Pointer :: struct { - elem: ^Type_Info, -} -Type_Info_Procedure :: struct { - params: ^Type_Info, // Type_Info_Parameters - results: ^Type_Info, // Type_Info_Parameters - 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, - is_sparse: bool, -} -Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int} -Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int} - -Type_Info_Parameters :: struct { // Only used for procedures parameters and results - types: []^Type_Info, - names: []string, -} -Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually - -Type_Info_Struct :: struct { - types: []^Type_Info, - names: []string, - offsets: []uintptr, - usings: []bool, - tags: []string, - is_packed: bool, - is_raw_union: bool, - is_no_copy: bool, - custom_align: bool, - - equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set - - // 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, - - equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set - - custom_align: bool, - no_nil: bool, - shared_nil: 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, - map_info: ^Map_Info, -} -Type_Info_Bit_Set :: struct { - elem: ^Type_Info, - underlying: ^Type_Info, // Possibly nil - lower: i64, - upper: i64, -} -Type_Info_Simd_Vector :: struct { - elem: ^Type_Info, - elem_size: int, - count: int, -} -Type_Info_Relative_Pointer :: struct { - pointer: ^Type_Info, // ^T - base_integer: ^Type_Info, -} -Type_Info_Relative_Multi_Pointer :: struct { - pointer: ^Type_Info, // [^]T - base_integer: ^Type_Info, -} -Type_Info_Matrix :: struct { - elem: ^Type_Info, - elem_size: int, - elem_stride: int, // elem_stride >= row_count - row_count: int, - column_count: int, - // Total element count = column_count * elem_stride -} -Type_Info_Soa_Pointer :: struct { - elem: ^Type_Info, -} - -Type_Info_Flag :: enum u8 { - Comparable = 0, - Simple_Compare = 1, -} -Type_Info_Flags :: distinct bit_set[Type_Info_Flag; u32] - -Type_Info :: struct { - size: int, - align: int, - flags: Type_Info_Flags, - 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_Multi_Pointer, - Type_Info_Procedure, - Type_Info_Array, - Type_Info_Enumerated_Array, - Type_Info_Dynamic_Array, - Type_Info_Slice, - Type_Info_Parameters, - Type_Info_Struct, - Type_Info_Union, - Type_Info_Enum, - Type_Info_Map, - Type_Info_Bit_Set, - Type_Info_Simd_Vector, - Type_Info_Relative_Pointer, - Type_Info_Relative_Multi_Pointer, - Type_Info_Matrix, - Type_Info_Soa_Pointer, - }, -} - -// NOTE(bill): This must match the compiler's -Typeid_Kind :: enum u8 { - Invalid, - Integer, - Rune, - Float, - Complex, - Quaternion, - String, - Boolean, - Any, - Type_Id, - Pointer, - Multi_Pointer, - Procedure, - Array, - Enumerated_Array, - Dynamic_Array, - Slice, - Tuple, - Struct, - Union, - Enum, - Map, - Bit_Set, - Simd_Vector, - Relative_Pointer, - Relative_Multi_Pointer, - Matrix, - Soa_Pointer, -} -#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 - -when ODIN_OS == .Windows { - // NOTE(Jeroen): If we're a Windows DLL, fwdReason will be populated. - // This tells a DLL if it's first loaded, about to be unloaded, or a thread is joining/exiting. - - DLL_Forward_Reason :: enum u32 { - Process_Detach = 0, // About to unload DLL - Process_Attach = 1, // Entry point - Thread_Attach = 2, - Thread_Detach = 3, - } - dll_forward_reason: DLL_Forward_Reason -} - -// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it) - - -Source_Code_Location :: struct { - file_path: string, - line, column: i32, - procedure: string, -} - -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, - Alloc_Non_Zeroed, - Resize_Non_Zeroed, -} - -Allocator_Mode_Set :: distinct bit_set[Allocator_Mode] - -Allocator_Query_Info :: struct { - pointer: rawptr, - size: Maybe(int), - alignment: Maybe(int), -} - -Allocator_Error :: enum byte { - None = 0, - Out_Of_Memory = 1, - Invalid_Pointer = 2, - Invalid_Argument = 3, - Mode_Not_Implemented = 4, -} - -Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, - location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error) -Allocator :: struct { - procedure: Allocator_Proc, - data: rawptr, -} - -Byte :: 1 -Kilobyte :: 1024 * Byte -Megabyte :: 1024 * Kilobyte -Gigabyte :: 1024 * Megabyte -Terabyte :: 1024 * Gigabyte -Petabyte :: 1024 * Terabyte -Exabyte :: 1024 * Petabyte - -// 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, - - user_ptr: rawptr, - user_index: int, - - // Internal use only - _internal: rawptr, -} - - -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, -} - -// The raw, type-erased representation of a map. -// -// 32-bytes on 64-bit -// 16-bytes on 32-bit -Raw_Map :: struct { - // A single allocation spanning all keys, values, and hashes. - // { - // k: Map_Cell(K) * (capacity / ks_per_cell) - // v: Map_Cell(V) * (capacity / vs_per_cell) - // h: Map_Cell(H) * (capacity / hs_per_cell) - // } - // - // The data is allocated assuming 64-byte alignment, meaning the address is - // always a multiple of 64. This means we have 6 bits of zeros in the pointer - // to store the capacity. We can store a value as large as 2^6-1 or 63 in - // there. This conveniently is the maximum log2 capacity we can have for a map - // as Odin uses signed integers to represent capacity. - // - // Since the hashes are backed by Map_Hash, which is just a 64-bit unsigned - // integer, the cell structure for hashes is unnecessary because 64/8 is 8 and - // requires no padding, meaning it can be indexed as a regular array of - // Map_Hash directly, though for consistency sake it's written as if it were - // an array of Map_Cell(Map_Hash). - data: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits - len: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits - allocator: Allocator, // 16-bytes on 64-bits, 8-bytes on 32-bits -} - -Raw_Any :: struct { - data: rawptr, - id: typeid, -} - -Raw_Cstring :: struct { - data: [^]byte, -} - -Raw_Soa_Pointer :: struct { - data: rawptr, - index: int, -} - - - -/* - // Defined internally by the compiler - Odin_OS_Type :: enum int { - Unknown, - Windows, - Darwin, - Linux, - Essence, - FreeBSD, - OpenBSD, - WASI, - JS, - Freestanding, - } -*/ -Odin_OS_Type :: type_of(ODIN_OS) - -/* - // Defined internally by the compiler - Odin_Arch_Type :: enum int { - Unknown, - amd64, - i386, - arm32, - arm64, - wasm32, - wasm64p32, - } -*/ -Odin_Arch_Type :: type_of(ODIN_ARCH) - -/* - // Defined internally by the compiler - Odin_Build_Mode_Type :: enum int { - Executable, - Dynamic, - Object, - Assembly, - LLVM_IR, - } -*/ -Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE) - -/* - // Defined internally by the compiler - Odin_Endian_Type :: enum int { - Unknown, - Little, - Big, - } -*/ -Odin_Endian_Type :: type_of(ODIN_ENDIAN) - - -/* - // Defined internally by the compiler - Odin_Platform_Subtarget_Type :: enum int { - Default, - iOS, - } -*/ -Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) - -/* - // Defined internally by the compiler - Odin_Sanitizer_Flag :: enum u32 { - Address = 0, - Memory = 1, - Thread = 2, - } - Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32] - - ODIN_SANITIZER_FLAGS // is a constant -*/ -Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS) - - -///////////////////////////// -// Init Startup Procedures // -///////////////////////////// - -// IMPORTANT NOTE(bill): Do not call this unless you want to explicitly set up the entry point and how it gets called -// This is probably only useful for freestanding targets -foreign { - @(link_name="__$startup_runtime") - _startup_runtime :: proc "odin" () --- - @(link_name="__$cleanup_runtime") - _cleanup_runtime :: proc "odin" () --- -} - -_cleanup_runtime_contextless :: proc "contextless" () { - context = default_context() - _cleanup_runtime() -} - - -///////////////////////////// -///////////////////////////// -///////////////////////////// - - -type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { - if info == nil { - 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 { - 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: break loop - } - } - return base -} -type_info_base_without_enum :: type_info_core - -__type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check { - MASK :: 1<<(8*size_of(typeid) - 8) - 1 - data := transmute(uintptr)id - n := int(data & MASK) - if n < 0 || n >= len(type_table) { - n = 0 - } - return &type_table[n] -} - -when !ODIN_NO_RTTI { - 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_core(type_info_of(id)) - return ti.id - } - typeid_base_without_enum :: typeid_core -} - - - -debug_trap :: intrinsics.debug_trap -trap :: intrinsics.trap -read_cycle_counter :: intrinsics.read_cycle_counter - - - -default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location) { - // 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 { - return - } - c^ = other^ - __init_context(c) -} - -@private -__init_context :: proc "contextless" (c: ^Context) { - if c == nil { - return - } - - // NOTE(bill): Do not initialize these procedures with a call as they are not defined with the "contextless" calling convention - c.allocator.procedure = default_allocator_proc - c.allocator.data = nil - - c.temp_allocator.procedure = default_temp_allocator_proc - when !NO_DEFAULT_TEMP_ALLOCATOR { - c.temp_allocator.data = &global_default_temp_allocator_data - } - - when !ODIN_DISABLE_ASSERT { - c.assertion_failure_proc = default_assertion_failure_proc - } - - c.logger.procedure = default_logger_proc - c.logger.data = nil -} - -default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! { - when ODIN_OS == .Freestanding { - // Do nothing - } else { - when !ODIN_DISABLE_ASSERT { - print_caller_location(loc) - print_string(" ") - } - print_string(prefix) - if len(message) > 0 { - print_string(": ") - print_string(message) - } - print_byte('\n') - } - trap() -} diff --git a/core/runtime/core_builtin.odin b/core/runtime/core_builtin.odin deleted file mode 100644 index 3f4ebbc74..000000000 --- a/core/runtime/core_builtin.odin +++ /dev/null @@ -1,915 +0,0 @@ -package runtime - -import "core:intrinsics" - -@builtin -Maybe :: union($T: typeid) {T} - - -@(builtin, require_results) -container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T - where intrinsics.type_has_field(T, field_name), - intrinsics.type_field_type(T, field_name) == Field_Type { - offset :: offset_of_by_string(T, field_name) - return (^T)(uintptr(ptr) - offset) if ptr != nil else nil -} - - -when !NO_DEFAULT_TEMP_ALLOCATOR { - @thread_local global_default_temp_allocator_data: Default_Temp_Allocator -} - -@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR) -init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) { - when !NO_DEFAULT_TEMP_ALLOCATOR { - default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator) - } -} - - -// `copy_slice` is a built-in procedure that copies elements from a source slice `src` to a destination slice `dst`. -// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum -// of len(src) and len(dst). -// -// Prefer the procedure group `copy`. -@builtin -copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { - n := max(0, min(len(dst), len(src))) - if n > 0 { - intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E)) - } - return n -} -// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`. -// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum -// of len(src) and len(dst). -// -// Prefer the procedure group `copy`. -@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 { - intrinsics.mem_copy(raw_data(dst), raw_data(src), n) - } - return n -} -// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`. -// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum -// of len(src) and len(dst). -@builtin -copy :: proc{copy_slice, copy_from_string} - - - -// `unordered_remove` removed the element at the specified `index`. It does so by replacing the current end value -// with the old value, and reducing the length of the dynamic array by 1. -// -// Note: This is an O(1) operation. -// Note: If you the elements to remain in their order, use `ordered_remove`. -// Note: If the index is out of bounds, this procedure will panic. -@builtin -unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { - bounds_check_error_loc(loc, index, len(array)) - n := len(array)-1 - if index != n { - array[index] = array[n] - } - (^Raw_Dynamic_Array)(array).len -= 1 -} -// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements. -// -// Note: This is an O(N) operation. -// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`. -// Note: If the index is out of bounds, this procedure will panic. -@builtin -ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { - bounds_check_error_loc(loc, index, len(array)) - if index+1 < len(array) { - copy(array[index:], array[index+1:]) - } - (^Raw_Dynamic_Array)(array).len -= 1 -} - -// `remove_range` removes a range of elements specified by the range `lo` and `hi`, whilst keeping the order of the other elements. -// -// Note: This is an O(N) operation. -// Note: If the range is out of bounds, this procedure will panic. -@builtin -remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_location) #no_bounds_check { - slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) - n := max(hi-lo, 0) - if n > 0 { - if hi != len(array) { - copy(array[lo:], array[hi:]) - } - (^Raw_Dynamic_Array)(array).len -= n - } -} - - -// `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. -// -// Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. -@builtin -pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { - assert(len(array) > 0, loc=loc) - res = array[len(array)-1] - (^Raw_Dynamic_Array)(array).len -= 1 - return res -} - - -// `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. -// If the operation is not possible, it will return false. -@builtin -pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { - if len(array) == 0 { - return - } - res, ok = array[len(array)-1], true - (^Raw_Dynamic_Array)(array).len -= 1 - return -} - -// `pop_front` will remove and return the first value of dynamic array `array` and reduces the length of `array` by 1. -// -// Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. -@builtin -pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { - assert(len(array) > 0, loc=loc) - res = array[0] - if len(array) > 1 { - copy(array[0:], array[1:]) - } - (^Raw_Dynamic_Array)(array).len -= 1 - return res -} - -// `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. -// If the operation is not possible, it will return false. -@builtin -pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { - if len(array) == 0 { - return - } - res, ok = array[0], true - if len(array) > 1 { - copy(array[0:], array[1:]) - } - (^Raw_Dynamic_Array)(array).len -= 1 - return -} - - -// `clear` will set the length of a passed dynamic array or map to `0` -@builtin -clear :: proc{clear_dynamic_array, clear_map} - -// `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). -@builtin -reserve :: proc{reserve_dynamic_array, reserve_map} - -@builtin -non_zero_reserve :: proc{non_zero_reserve_dynamic_array} - -// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`). -@builtin -resize :: proc{resize_dynamic_array} - -@builtin -non_zero_resize :: proc{non_zero_resize_dynamic_array} - -// Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity. -@builtin -shrink :: proc{shrink_dynamic_array, shrink_map} - -// `free` will try to free the passed pointer, with the given `allocator` if the allocator supports this operation. -@builtin -free :: proc{mem_free} - -// `free_all` will try to free/reset all of the memory of the given `allocator` if the allocator supports this operation. -@builtin -free_all :: proc{mem_free_all} - - - -// `delete_string` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return mem_free_with_size(raw_data(str), len(str), allocator, loc) -} -// `delete_cstring` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return mem_free((^byte)(str), allocator, loc) -} -// `delete_dynamic_array` will try to free the underlying data of the passed dynamic array, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error { - return mem_free_with_size(raw_data(array), cap(array)*size_of(E), array.allocator, loc) -} -// `delete_slice` will try to free the underlying data of the passed sliced, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return mem_free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc) -} -// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error { - return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc) -} - - -// `delete` will try to free the underlying data of the passed built-in data structure (string, cstring, dynamic array, slice, or map), with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer `delete` over the specific `delete_*` procedures where possible. -@builtin -delete :: proc{ - delete_string, - delete_cstring, - delete_dynamic_array, - delete_slice, - delete_map, - delete_soa_slice, - delete_soa_dynamic_array, -} - - -// The new built-in procedure allocates memory. The first argument is a type, not a value, and the value -// return is a pointer to a newly allocated value of that type using the specified allocator, default is context.allocator -@(builtin, require_results) -new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) #optional_allocator_error { - return new_aligned(T, align_of(T), allocator, loc) -} -@(require_results) -new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { - data := mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return - t = (^T)(raw_data(data)) - return -} - -@(builtin, require_results) -new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error { - t_data := mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return - t = (^T)(raw_data(t_data)) - if t != nil { - t^ = data - } - return -} - -DEFAULT_RESERVE_CAPACITY :: 16 - -@(require_results) -make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - make_slice_error_loc(loc, len) - data, err := mem_alloc_bytes(size_of(E)*len, alignment, allocator, loc) - if data == nil && size_of(E) != 0 { - return nil, err - } - s := Raw_Slice{raw_data(data), len} - return transmute(T)s, err -} - -// `make_slice` allocates and initializes a slice. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_aligned(T, len, align_of(E), allocator, loc) -} -// `make_dynamic_array` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc) -} -// `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_dynamic_array_len_cap(T, len, len, allocator, loc) -} -// `make_dynamic_array_len_cap` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - make_dynamic_array_error_loc(loc, len, cap) - data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return - s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator} - if data == nil && size_of(E) != 0 { - s.len, s.cap = 0, 0 - } - array = transmute(T)s - return -} -// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { - make_map_expr_error_loc(loc, capacity) - context.allocator = allocator - - err = reserve_map(&m, capacity, loc) - return -} -// `make_multi_pointer` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// This is "similar" to doing `raw_data(make([]E, len, allocator))`. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error { - make_slice_error_loc(loc, len) - data := mem_alloc_bytes(size_of(E)*len, align_of(E), allocator, loc) or_return - if data == nil && size_of(E) != 0 { - return - } - mp = cast(T)raw_data(data) - return -} - - -// `make` built-in procedure allocates and initializes a value of type slice, dynamic array, map, or multi-pointer (only). -// -// Similar to `new`, the first argument is a type, not a value. Unlike new, make's return type is the same as the -// type of its argument, not a pointer to it. -// Make uses the specified allocator, default is context.allocator. -@builtin -make :: proc{ - make_slice, - make_dynamic_array, - make_dynamic_array_len, - make_dynamic_array_len_cap, - make_map, - make_multi_pointer, -} - - - -// `clear_map` will set the length of a passed map to `0` -// -// Note: Prefer the procedure group `clear` -@builtin -clear_map :: proc "contextless" (m: ^$T/map[$K]$V) { - if m == nil { - return - } - map_clear_dynamic((^Raw_Map)(m), map_info(T)) -} - -// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`). -// -// Note: Prefer the procedure group `reserve` -@builtin -reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error { - return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil -} - -// Shrinks the capacity of a map down to the current length. -// -// Note: Prefer the procedure group `shrink` -@builtin -shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { - if m != nil { - return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc) - } - return -} - -// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map. -// If m is nil, or there is no such element, this procedure is a no-op -@builtin -delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) { - if m != nil { - key := key - old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key)) - if ok { - deleted_key = (^K)(old_k)^ - deleted_value = (^V)(old_v)^ - } - } - return -} - -_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += 1 - return 1, nil - } else { - if cap(array) < len(array)+1 { - cap := 2 * cap(array) + max(8, 1) - - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - if cap(array)-len(array) > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - data[a.len] = arg - } - a.len += 1 - return 1, err - } - return 0, err - } -} - -@builtin -append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, true, loc=loc) -} - -@builtin -non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, false, loc=loc) -} - -_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - - arg_len := len(args) - if arg_len <= 0 { - return 0, nil - } - - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += arg_len - return arg_len, nil - } else { - if cap(array) < len(array)+arg_len { - cap := 2 * cap(array) + max(8, arg_len) - - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - arg_len = min(cap(array)-len(array), arg_len) - if arg_len > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) - } - a.len += arg_len - } - return arg_len, err - } -} - -@builtin -append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, true, loc, ..args) -} - -@builtin -non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, false, loc, ..args) -} - -// The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type -_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - args := transmute([]E)arg - if should_zero { - return append_elems(array, ..args, loc=loc) - } else { - return non_zero_append_elems(array, ..args, loc=loc) - } -} - -@builtin -append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem_string(array, arg, true, loc) -} -@builtin -non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem_string(array, arg, false, loc) -} - - -// The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type -@builtin -append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - n_arg: int - for arg in args { - n_arg, err = append(array, ..transmute([]E)(arg), loc=loc) - n += n_arg - if err != nil { - return - } - } - return -} - -// The append built-in procedure appends elements to the end of a dynamic array -@builtin append :: proc{append_elem, append_elems, append_elem_string} -@builtin non_zero_append :: proc{non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string} - - -@builtin -append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - prev_len := len(array) - resize(array, len(array)+1, loc) or_return - return len(array)-prev_len, nil -} - - -@builtin -inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if array == nil { - return - } - n := max(len(array), index) - m :: 1 - new_size := n + m - - resize(array, new_size, loc) or_return - when size_of(E) != 0 { - copy(array[index + m:], array[index:]) - array[index] = arg - } - ok = true - return -} - -@builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if array == nil { - return - } - if len(args) == 0 { - ok = true - return - } - - n := max(len(array), index) - m := len(args) - new_size := n + m - - resize(array, new_size, loc) or_return - when size_of(E) != 0 { - copy(array[index + m:], array[index:]) - copy(array[index:], args) - } - ok = true - return -} - -@builtin -inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if array == nil { - return - } - if len(arg) == 0 { - ok = true - return - } - - n := max(len(array), index) - m := len(arg) - new_size := n + m - - resize(array, new_size, loc) or_return - copy(array[index+m:], array[index:]) - copy(array[index:], arg) - ok = true - return -} - -@builtin inject_at :: proc{inject_at_elem, inject_at_elems, inject_at_elem_string} - - - -@builtin -assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if index < len(array) { - array[index] = arg - ok = true - } else { - resize(array, index+1, loc) or_return - array[index] = arg - ok = true - } - return -} - - -@builtin -assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - new_size := index + len(args) - if len(args) == 0 { - ok = true - } else if new_size < len(array) { - copy(array[index:], args) - ok = true - } else { - resize(array, new_size, loc) or_return - copy(array[index:], args) - ok = true - } - return -} - - -@builtin -assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - new_size := index + len(arg) - if len(arg) == 0 { - ok = true - } else if new_size < len(array) { - copy(array[index:], arg) - ok = true - } else { - resize(array, new_size, loc) or_return - copy(array[index:], arg) - ok = true - } - return -} - -@builtin assign_at :: proc{assign_at_elem, assign_at_elems, assign_at_elem_string} - - - - -// `clear_dynamic_array` will set the length of a passed dynamic array to `0` -// -// Note: Prefer the procedure group `clear`. -@builtin -clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { - if array != nil { - (^Raw_Dynamic_Array)(array).len = 0 - } -} - -// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). -// -// Note: Prefer the procedure group `reserve`. -_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - a := (^Raw_Dynamic_Array)(array) - - if capacity <= a.cap { - return nil - } - - 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: []byte - if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } - if new_data == nil && new_size > 0 { - return .Out_Of_Memory - } - - a.data = raw_data(new_data) - a.cap = capacity - return nil -} - -@builtin -reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, true, loc) -} - -@builtin -non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, false, loc) -} - -// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). -// -// Note: Prefer the procedure group `resize` -_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - a := (^Raw_Dynamic_Array)(array) - - if length <= a.cap { - a.len = max(length, 0) - return nil - } - - 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 : []byte - if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } - if new_data == nil && new_size > 0 { - return .Out_Of_Memory - } - - a.data = raw_data(new_data) - a.len = length - a.cap = length - return nil -} - -@builtin -resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, true, loc=loc) -} - -@builtin -non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, false, loc=loc) -} - -/* - Shrinks the capacity of a dynamic array down to the current length, or the given capacity. - - If `new_cap` is negative, then `len(array)` is used. - - Returns false if `cap(array) < new_cap`, or the allocator report failure. - - If `len(array) < new_cap`, then `len(array)` will be left unchanged. - - Note: Prefer the procedure group `shrink` -*/ -shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { - if array == nil { - return - } - a := (^Raw_Dynamic_Array)(array) - - new_cap := new_cap if new_cap >= 0 else a.len - - if new_cap > a.cap { - return - } - - if a.allocator.procedure == nil { - a.allocator = context.allocator - } - assert(a.allocator.procedure != nil) - - old_size := a.cap * size_of(E) - new_size := new_cap * size_of(E) - - new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return - - a.data = raw_data(new_data) - a.len = min(new_cap, a.len) - a.cap = new_cap - return true, nil -} - -@builtin -map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) { - key, value := key, value - return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)) -} - - -@builtin -incl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { - s^ |= {elem} -} -@builtin -incl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { - for elem in elems { - s^ |= {elem} - } -} -@builtin -incl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { - s^ |= other -} -@builtin -excl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { - s^ &~= {elem} -} -@builtin -excl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { - for elem in elems { - s^ &~= {elem} - } -} -@builtin -excl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { - s^ &~= other -} - -@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 { - return int(intrinsics.count_ones(transmute(u8)s)) - } else when size_of(S) == 2 { - return int(intrinsics.count_ones(transmute(u16)s)) - } else when size_of(S) == 4 { - return int(intrinsics.count_ones(transmute(u32)s)) - } else when size_of(S) == 8 { - return int(intrinsics.count_ones(transmute(u64)s)) - } else when size_of(S) == 16 { - return int(intrinsics.count_ones(transmute(u128)s)) - } else { - #panic("Unhandled card bit_set size") - } -} - - - -@builtin -@(disabled=ODIN_DISABLE_ASSERT) -assert :: proc(condition: bool, message := "", loc := #caller_location) { - if !condition { - // NOTE(bill): This is wrapped in a procedure call - // to improve performance to make the CPU not - // execute speculatively, making it about an order of - // magnitude faster - @(cold) - internal :: 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) - } - internal(message, loc) - } -} - -@builtin -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 -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) -} diff --git a/core/runtime/core_builtin_matrix.odin b/core/runtime/core_builtin_matrix.odin deleted file mode 100644 index 7d60d625c..000000000 --- a/core/runtime/core_builtin_matrix.odin +++ /dev/null @@ -1,274 +0,0 @@ -package runtime - -import "core:intrinsics" -_ :: intrinsics - - -@(builtin) -determinant :: proc{ - matrix1x1_determinant, - matrix2x2_determinant, - matrix3x3_determinant, - matrix4x4_determinant, -} - -@(builtin) -adjugate :: proc{ - matrix1x1_adjugate, - matrix2x2_adjugate, - matrix3x3_adjugate, - matrix4x4_adjugate, -} - -@(builtin) -inverse_transpose :: proc{ - matrix1x1_inverse_transpose, - matrix2x2_inverse_transpose, - matrix3x3_inverse_transpose, - matrix4x4_inverse_transpose, -} - - -@(builtin) -inverse :: proc{ - matrix1x1_inverse, - matrix2x2_inverse, - matrix3x3_inverse, - matrix4x4_inverse, -} - -@(builtin, require_results) -hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 { - return conj(transpose(m)) -} - -@(builtin, require_results) -matrix_trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) { - for i in 0.. (minor: T) where N > 1 { - K :: N-1 - cut_down: matrix[K, K]T - for col_idx in 0..= column) - for row_idx in 0..= row) - cut_down[row_idx, col_idx] = m[i, j] - } - } - return determinant(cut_down) -} - - - -@(builtin, require_results) -matrix1x1_determinant :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) { - return m[0, 0] -} - -@(builtin, require_results) -matrix2x2_determinant :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) { - return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] -} -@(builtin, require_results) -matrix3x3_determinant :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) { - a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) - b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) - c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) - return a + b + c -} -@(builtin, require_results) -matrix4x4_determinant :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) { - a := adjugate(m) - #no_bounds_check for i in 0..<4 { - det += m[0, i] * a[0, i] - } - return -} - - - - -@(builtin, require_results) -matrix1x1_adjugate :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y = x - return -} - -@(builtin, require_results) -matrix2x2_adjugate :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - y[0, 0] = +x[1, 1] - y[0, 1] = -x[1, 0] - y[1, 0] = -x[0, 1] - y[1, 1] = +x[0, 0] - return -} - -@(builtin, require_results) -matrix3x3_adjugate :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { - y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) - y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) - y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) - y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) - y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) - y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) - y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) - y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) - y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) - return -} - - -@(builtin, require_results) -matrix4x4_adjugate :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { - for i in 0..<4 { - for j in 0..<4 { - sign: T = 1 if (i + j) % 2 == 0 else -1 - y[i, j] = sign * matrix_minor(x, i, j) - } - } - return -} - -@(builtin, require_results) -matrix1x1_inverse_transpose :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y[0, 0] = 1/x[0, 0] - return -} - -@(builtin, require_results) -matrix2x2_inverse_transpose :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] - when intrinsics.type_is_integer(T) { - y[0, 0] = +x[1, 1] / d - y[1, 0] = -x[0, 1] / d - y[0, 1] = -x[1, 0] / d - y[1, 1] = +x[0, 0] / d - } else { - id := 1 / d - y[0, 0] = +x[1, 1] * id - y[1, 0] = -x[0, 1] * id - y[0, 1] = -x[1, 0] * id - y[1, 1] = +x[0, 0] * id - } - return -} - -@(builtin, require_results) -matrix3x3_inverse_transpose :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d := determinant(x) - when intrinsics.type_is_integer(T) { - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[i, j] / d - } - } - } else { - id := 1/d - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[i, j] * id - } - } - } - return -} - -@(builtin, require_results) -matrix4x4_inverse_transpose :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d: T - for i in 0..<4 { - d += x[0, i] * a[0, i] - } - when intrinsics.type_is_integer(T) { - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[i, j] / d - } - } - } else { - id := 1/d - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[i, j] * id - } - } - } - return -} - -@(builtin, require_results) -matrix1x1_inverse :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y[0, 0] = 1/x[0, 0] - return -} - -@(builtin, require_results) -matrix2x2_inverse :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] - when intrinsics.type_is_integer(T) { - y[0, 0] = +x[1, 1] / d - y[0, 1] = -x[0, 1] / d - y[1, 0] = -x[1, 0] / d - y[1, 1] = +x[0, 0] / d - } else { - id := 1 / d - y[0, 0] = +x[1, 1] * id - y[0, 1] = -x[0, 1] * id - y[1, 0] = -x[1, 0] * id - y[1, 1] = +x[0, 0] * id - } - return -} - -@(builtin, require_results) -matrix3x3_inverse :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d := determinant(x) - when intrinsics.type_is_integer(T) { - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[j, i] / d - } - } - } else { - id := 1/d - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[j, i] * id - } - } - } - return -} - -@(builtin, require_results) -matrix4x4_inverse :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d: T - for i in 0..<4 { - d += x[0, i] * a[0, i] - } - when intrinsics.type_is_integer(T) { - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[j, i] / d - } - } - } else { - id := 1/d - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[j, i] * id - } - } - } - return -} diff --git a/core/runtime/core_builtin_soa.odin b/core/runtime/core_builtin_soa.odin deleted file mode 100644 index 6313a28f5..000000000 --- a/core/runtime/core_builtin_soa.odin +++ /dev/null @@ -1,428 +0,0 @@ -package runtime - -import "core:intrinsics" -_ :: intrinsics - -/* - - SOA types are implemented with this sort of layout: - - SOA Fixed Array - struct { - f0: [N]T0, - f1: [N]T1, - f2: [N]T2, - } - - SOA Slice - struct { - f0: ^T0, - f1: ^T1, - f2: ^T2, - - len: int, - } - - SOA Dynamic Array - struct { - f0: ^T0, - f1: ^T1, - f2: ^T2, - - len: int, - cap: int, - allocator: Allocator, - } - - A footer is used rather than a header purely to simplify access to the fields internally - i.e. field index of the AOS == SOA - -*/ - - -Raw_SOA_Footer_Slice :: struct { - len: int, -} - -Raw_SOA_Footer_Dynamic_Array :: struct { - len: int, - cap: int, - allocator: Allocator, -} - -@(builtin, require_results) -raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { - if array == nil { - return nil - } - field_count := uintptr(intrinsics.type_struct_field_count(E)) - footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr)) - return -} -@(builtin, require_results) -raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { - if array == nil { - return nil - } - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } - footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr)) - return -} -raw_soa_footer :: proc{ - raw_soa_footer_slice, - raw_soa_footer_dynamic_array, -} - - - -@(builtin, require_results) -make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - if length <= 0 { - return - } - - footer := raw_soa_footer(&array) - if size_of(E) == 0 { - footer.len = length - return - } - - max_align := max(alignment, align_of(E)) - - ti := type_info_of(typeid_of(T)) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - - field_count := uintptr(intrinsics.type_struct_field_count(E)) - - total_size := 0 - for i in 0.. (array: T, err: Allocator_Error) #optional_allocator_error { - return make_soa_aligned(T, length, align_of(E), allocator, loc) -} - -@(builtin, require_results) -make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - context.allocator = allocator - reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return - return array, nil -} - -@(builtin, require_results) -make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - context.allocator = allocator - resize_soa(&array, length, loc) or_return - return array, nil -} - -@(builtin, require_results) -make_soa_dynamic_array_len_cap :: proc($T: typeid/#soa[dynamic]$E, #any_int length, capacity: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - context.allocator = allocator - reserve_soa(&array, capacity, loc) or_return - resize_soa(&array, length, loc) or_return - return array, nil -} - - -@builtin -make_soa :: proc{ - make_soa_slice, - make_soa_dynamic_array, - make_soa_dynamic_array_len, - make_soa_dynamic_array_len_cap, -} - - -@builtin -resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - reserve_soa(array, length, loc) or_return - footer := raw_soa_footer(array) - footer.len = length - return nil -} - -@builtin -reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - - old_cap := cap(array) - if capacity <= old_cap { - return nil - } - - if array.allocator.procedure == nil { - array.allocator = context.allocator - } - assert(array.allocator.procedure != nil) - - footer := raw_soa_footer(array) - if size_of(E) == 0 { - footer.cap = capacity - return nil - } - - ti := type_info_of(typeid_of(T)) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } - assert(footer.cap == old_cap) - - old_size := 0 - new_size := 0 - - max_align :: align_of(E) - for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - - if cap(array) <= len(array) + 1 { - cap := 2 * cap(array) + 8 - err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success - } - - footer := raw_soa_footer(array) - - if size_of(E) > 0 && cap(array)-len(array) > 0 { - ti := type_info_of(T) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } - - data := (^rawptr)(array)^ - - soa_offset := 0 - item_offset := 0 - - arg_copy := arg - arg_ptr := &arg_copy - - max_align :: align_of(E) - for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return - } - - arg_len := len(args) - if arg_len == 0 { - return - } - - if cap(array) <= len(array)+arg_len { - cap := 2 * cap(array) + max(8, arg_len) - err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success - } - arg_len = min(cap(array)-len(array), arg_len) - - footer := raw_soa_footer(array) - if size_of(E) > 0 && arg_len > 0 { - ti := type_info_of(typeid_of(T)) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - field_count := uintptr(intrinsics.type_struct_field_count(E)) - - data := (^rawptr)(array)^ - - soa_offset := 0 - item_offset := 0 - - args_ptr := &args[0] - - max_align :: align_of(E) - for i in 0.. Allocator_Error { - when intrinsics.type_struct_field_count(E) != 0 { - array := array - ptr := (^rawptr)(&array)^ - free(ptr, allocator, loc) or_return - } - return nil -} - -delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error { - when intrinsics.type_struct_field_count(E) != 0 { - array := array - ptr := (^rawptr)(&array)^ - footer := raw_soa_footer(&array) - free(ptr, footer.allocator, loc) or_return - } - return nil -} - - -@builtin -delete_soa :: proc{ - delete_soa_slice, - delete_soa_dynamic_array, -} - - -clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) { - when intrinsics.type_struct_field_count(E) != 0 { - footer := raw_soa_footer(array) - footer.len = 0 - } -} - -@builtin -clear_soa :: proc{ - clear_soa_dynamic_array, -} \ No newline at end of file diff --git a/core/runtime/default_allocators_arena.odin b/core/runtime/default_allocators_arena.odin deleted file mode 100644 index 1fe3c6cfc..000000000 --- a/core/runtime/default_allocators_arena.odin +++ /dev/null @@ -1,304 +0,0 @@ -package runtime - -import "core:intrinsics" - -DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) - -Memory_Block :: struct { - prev: ^Memory_Block, - allocator: Allocator, - base: [^]byte, - used: uint, - capacity: uint, -} - -Arena :: struct { - backing_allocator: Allocator, - curr_block: ^Memory_Block, - total_used: uint, - total_capacity: uint, - minimum_block_size: uint, - temp_count: uint, -} - -@(private, require_results) -safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) { - z, did_overflow := intrinsics.overflow_add(x, y) - return z, !did_overflow -} - -@(require_results) -memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) { - total_size := uint(capacity + max(alignment, size_of(Memory_Block))) - base_offset := uintptr(max(alignment, size_of(Memory_Block))) - - min_alignment: int = max(16, align_of(Memory_Block), int(alignment)) - data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return - block = (^Memory_Block)(raw_data(data)) - end := uintptr(raw_data(data)[len(data):]) - - block.allocator = allocator - block.base = ([^]byte)(uintptr(block) + base_offset) - block.capacity = uint(end - uintptr(block.base)) - - // Should be zeroed - assert(block.used == 0) - assert(block.prev == nil) - return -} - -memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { - if block_to_free != nil { - allocator := block_to_free.allocator - mem_free(block_to_free, allocator, loc) - } -} - -@(require_results) -alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) { - calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { - alignment_offset := uint(0) - ptr := uintptr(block.base[block.used:]) - mask := alignment-1 - if ptr & mask != 0 { - alignment_offset = uint(alignment - (ptr & mask)) - } - return alignment_offset - - } - if block == nil { - return nil, .Out_Of_Memory - } - alignment_offset := calc_alignment_offset(block, uintptr(alignment)) - size, size_ok := safe_add(min_size, alignment_offset) - if !size_ok { - err = .Out_Of_Memory - return - } - - if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity { - err = .Out_Of_Memory - return - } - data = block.base[block.used+alignment_offset:][:min_size] - block.used += size - return -} - -@(require_results) -arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint { - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p - } - - assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) - - size := size - if size == 0 { - return - } - - needed := align_forward_uint(size, alignment) - if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.capacity { - if arena.minimum_block_size == 0 { - arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE - } - - block_size := max(needed, arena.minimum_block_size) - - if arena.backing_allocator.procedure == nil { - arena.backing_allocator = default_allocator() - } - - new_block := memory_block_alloc(arena.backing_allocator, block_size, alignment, loc) or_return - new_block.prev = arena.curr_block - arena.curr_block = new_block - arena.total_capacity += new_block.capacity - } - - prev_used := arena.curr_block.used - data, err = alloc_from_memory_block(arena.curr_block, size, alignment) - arena.total_used += arena.curr_block.used - prev_used - return -} - -// `arena_init` will initialize the arena with a usuable block. -// This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary -@(require_results) -arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error { - arena^ = {} - arena.backing_allocator = backing_allocator - arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB - new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, 0, loc) or_return - arena.curr_block = new_block - arena.total_capacity += new_block.capacity - return nil -} - - -arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { - if free_block := arena.curr_block; free_block != nil { - arena.curr_block = free_block.prev - - arena.total_capacity -= free_block.capacity - memory_block_dealloc(free_block, loc) - } -} - -// `arena_free_all` will free all but the first memory block, and then reset the memory block -arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { - for arena.curr_block != nil && arena.curr_block.prev != nil { - arena_free_last_memory_block(arena, loc) - } - - if arena.curr_block != nil { - intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) - arena.curr_block.used = 0 - } - arena.total_used = 0 -} - -arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { - for arena.curr_block != nil { - free_block := arena.curr_block - arena.curr_block = free_block.prev - - arena.total_capacity -= free_block.capacity - memory_block_dealloc(free_block, loc) - } - arena.total_used = 0 - arena.total_capacity = 0 -} - -arena_allocator :: proc(arena: ^Arena) -> Allocator { - return Allocator{arena_allocator_proc, arena} -} - -arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, - location := #caller_location) -> (data: []byte, err: Allocator_Error) { - arena := (^Arena)(allocator_data) - - size, alignment := uint(size), uint(alignment) - old_size := uint(old_size) - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return arena_alloc(arena, size, alignment, location) - case .Free: - err = .Mode_Not_Implemented - case .Free_All: - arena_free_all(arena, location) - case .Resize, .Resize_Non_Zeroed: - old_data := ([^]byte)(old_memory) - - switch { - case old_data == nil: - return arena_alloc(arena, size, alignment, location) - case size == old_size: - // return old memory - data = old_data[:size] - return - case size == 0: - err = .Mode_Not_Implemented - return - case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: - // shrink data in-place - data = old_data[:size] - return - } - - new_memory := arena_alloc(arena, size, alignment, location) or_return - if new_memory == nil { - return - } - copy(new_memory, old_data[:old_size]) - return new_memory, nil - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features} - } - case .Query_Info: - err = .Mode_Not_Implemented - } - - return -} - - - - -Arena_Temp :: struct { - arena: ^Arena, - block: ^Memory_Block, - used: uint, -} - -@(require_results) -arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { - assert(arena != nil, "nil arena", loc) - - temp.arena = arena - temp.block = arena.curr_block - if arena.curr_block != nil { - temp.used = arena.curr_block.used - } - arena.temp_count += 1 - return -} - -arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { - if temp.arena == nil { - assert(temp.block == nil) - assert(temp.used == 0) - return - } - arena := temp.arena - - if temp.block != nil { - memory_block_found := false - for block := arena.curr_block; block != nil; block = block.prev { - if block == temp.block { - memory_block_found = true - break - } - } - if !memory_block_found { - assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc) - } - - for arena.curr_block != temp.block { - arena_free_last_memory_block(arena) - } - - if block := arena.curr_block; block != nil { - assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) - amount_to_zero := min(block.used-temp.used, block.capacity-block.used) - intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) - block.used = temp.used - } - } - - assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) - arena.temp_count -= 1 -} - -// Ignore the use of a `arena_temp_begin` entirely -arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { - assert(temp.arena != nil, "nil arena", loc) - arena := temp.arena - - assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) - arena.temp_count -= 1 -} - -arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { - assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) -} diff --git a/core/runtime/default_allocators_general.odin b/core/runtime/default_allocators_general.odin deleted file mode 100644 index 994a672b0..000000000 --- a/core/runtime/default_allocators_general.odin +++ /dev/null @@ -1,23 +0,0 @@ -//+build !windows -//+build !freestanding -//+build !wasi -//+build !js -package runtime - -// TODO(bill): reimplement these procedures in the os_specific stuff -import "core:os" - -when ODIN_DEFAULT_TO_NIL_ALLOCATOR { - _ :: os - - // mem.nil_allocator reimplementation - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} else { - - default_allocator_proc :: os.heap_allocator_proc - - default_allocator :: proc() -> Allocator { - return os.heap_allocator() - } -} diff --git a/core/runtime/default_allocators_js.odin b/core/runtime/default_allocators_js.odin deleted file mode 100644 index 715073f08..000000000 --- a/core/runtime/default_allocators_js.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build js -package runtime - -default_allocator_proc :: panic_allocator_proc -default_allocator :: panic_allocator diff --git a/core/runtime/default_allocators_nil.odin b/core/runtime/default_allocators_nil.odin deleted file mode 100644 index c882f5196..000000000 --- a/core/runtime/default_allocators_nil.odin +++ /dev/null @@ -1,88 +0,0 @@ -package runtime - -nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return nil, .Out_Of_Memory - case .Free: - return nil, .None - case .Free_All: - return nil, .Mode_Not_Implemented - case .Resize, .Resize_Non_Zeroed: - if size == 0 { - return nil, .None - } - return nil, .Out_Of_Memory - case .Query_Features: - return nil, .Mode_Not_Implemented - case .Query_Info: - return nil, .Mode_Not_Implemented - } - return nil, .None -} - -nil_allocator :: proc() -> Allocator { - return Allocator{ - procedure = nil_allocator_proc, - data = nil, - } -} - - - -when ODIN_OS == .Freestanding { - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} - - - -panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - switch mode { - case .Alloc: - if size > 0 { - panic("panic allocator, .Alloc called", loc=loc) - } - case .Alloc_Non_Zeroed: - if size > 0 { - panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) - } - case .Resize: - if size > 0 { - panic("panic allocator, .Resize called", loc=loc) - } - case .Resize_Non_Zeroed: - if size > 0 { - panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) - } - case .Free: - if old_memory != nil { - panic("panic allocator, .Free called", loc=loc) - } - case .Free_All: - panic("panic allocator, .Free_All called", loc=loc) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Query_Features} - } - return nil, nil - - case .Query_Info: - panic("panic allocator, .Query_Info called", loc=loc) - } - - return nil, nil -} - -panic_allocator :: proc() -> Allocator { - return Allocator{ - procedure = panic_allocator_proc, - data = nil, - } -} diff --git a/core/runtime/default_allocators_wasi.odin b/core/runtime/default_allocators_wasi.odin deleted file mode 100644 index a7e6842a6..000000000 --- a/core/runtime/default_allocators_wasi.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build wasi -package runtime - -default_allocator_proc :: panic_allocator_proc -default_allocator :: panic_allocator diff --git a/core/runtime/default_allocators_windows.odin b/core/runtime/default_allocators_windows.odin deleted file mode 100644 index 1b0f78428..000000000 --- a/core/runtime/default_allocators_windows.odin +++ /dev/null @@ -1,44 +0,0 @@ -//+build windows -package runtime - -when ODIN_DEFAULT_TO_NIL_ALLOCATOR { - // mem.nil_allocator reimplementation - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} else { - default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - data, err = _windows_default_alloc(size, alignment, mode == .Alloc) - - case .Free: - _windows_default_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize, .Resize_Non_Zeroed: - data, err = _windows_default_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Query_Features} - } - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return - } - - default_allocator :: proc() -> Allocator { - return Allocator{ - procedure = default_allocator_proc, - data = nil, - } - } -} diff --git a/core/runtime/default_temporary_allocator.odin b/core/runtime/default_temporary_allocator.odin deleted file mode 100644 index c90f0388d..000000000 --- a/core/runtime/default_temporary_allocator.odin +++ /dev/null @@ -1,79 +0,0 @@ -package runtime - -DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte) -NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR - -when NO_DEFAULT_TEMP_ALLOCATOR { - Default_Temp_Allocator :: struct {} - - default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {} - - default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {} - - default_temp_allocator_proc :: nil_allocator_proc - - @(require_results) - default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { - return - } - - default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { - } -} else { - Default_Temp_Allocator :: struct { - arena: Arena, - } - - default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) { - _ = arena_init(&s.arena, uint(size), backing_allocator) - } - - default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) { - if s != nil { - arena_destroy(&s.arena) - s^ = {} - } - } - - default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - - s := (^Default_Temp_Allocator)(allocator_data) - return arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc) - } - - @(require_results) - default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { - if context.temp_allocator.data == &global_default_temp_allocator_data { - temp = arena_temp_begin(&global_default_temp_allocator_data.arena, loc) - } - return - } - - default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { - arena_temp_end(temp, loc) - } - - @(fini, private) - _destroy_temp_allocator_fini :: proc() { - default_temp_allocator_destroy(&global_default_temp_allocator_data) - } -} - -@(deferred_out=default_temp_allocator_temp_end) -DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (Arena_Temp, Source_Code_Location) { - if ignore { - return {}, loc - } else { - return default_temp_allocator_temp_begin(loc), loc - } -} - - -default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator { - return Allocator{ - procedure = default_temp_allocator_proc, - data = allocator, - } -} diff --git a/core/runtime/docs.odin b/core/runtime/docs.odin deleted file mode 100644 index a520584c5..000000000 --- a/core/runtime/docs.odin +++ /dev/null @@ -1,179 +0,0 @@ -package runtime - -/* - -package runtime has numerous entities (declarations) which are required by the compiler to function. - - -## Basic types and calls (and anything they rely on) - -Source_Code_Location -Context -Allocator -Logger - -__init_context -_cleanup_runtime - - -## cstring calls - -cstring_to_string -cstring_len - - - -## Required when RTTI is enabled (the vast majority of targets) - -Type_Info - -type_table -__type_info_of - - -## Hashing - -default_hasher -default_hasher_cstring -default_hasher_string - - -## Pseudo-CRT required procedured due to LLVM but useful in general -memset -memcpy -memove - - -## Procedures required by the LLVM backend -umodti3 -udivti3 -modti3 -divti3 -fixdfti -fixunsdfti -fixunsdfdi -floattidf -floattidf_unsigned -truncsfhf2 -truncdfhf2 -gnu_h2f_ieee -gnu_f2h_ieee -extendhfsf2 -__ashlti3 // wasm specific -__multi3 // wasm specific - - - -## Required an entry point is defined (i.e. 'main') - -args__ - - -## When -no-crt is defined (and not a wasm target) (mostly due to LLVM) -_tls_index -_fltused - - -## Bounds checking procedures (when not disabled with -no-bounds-check) - -bounds_check_error -matrix_bounds_check_error -slice_expr_error_hi -slice_expr_error_lo_hi -multi_pointer_slice_expr_error - - -## Type assertion check - -type_assertion_check -type_assertion_check2 // takes in typeid - - -## Arithmetic - -quo_complex32 -quo_complex64 -quo_complex128 - -mul_quaternion64 -mul_quaternion128 -mul_quaternion256 - -quo_quaternion64 -quo_quaternion128 -quo_quaternion256 - -abs_complex32 -abs_complex64 -abs_complex128 - -abs_quaternion64 -abs_quaternion128 -abs_quaternion256 - - -## Comparison - -memory_equal -memory_compare -memory_compare_zero - -cstring_eq -cstring_ne -cstring_lt -cstring_gt -cstring_le -cstring_gt - -string_eq -string_ne -string_lt -string_gt -string_le -string_gt - -complex32_eq -complex32_ne -complex64_eq -complex64_ne -complex128_eq -complex128_ne - -quaternion64_eq -quaternion64_ne -quaternion128_eq -quaternion128_ne -quaternion256_eq -quaternion256_ne - - -## Map specific calls - -map_seed_from_map_data -__dynamic_map_check_grow // static map calls -map_insert_hash_dynamic // static map calls -__dynamic_map_get // dynamic map calls -__dynamic_map_set // dynamic map calls - - -## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals) - -__dynamic_array_reserve -__dynamic_array_append - -__dynamic_map_reserve - - -## Objective-C specific - -objc_lookUpClass -sel_registerName -objc_allocateClassPair - - -## for-in `string` type - -string_decode_rune -string_decode_last_rune // #reverse for - -*/ \ No newline at end of file diff --git a/core/runtime/dynamic_array_internal.odin b/core/runtime/dynamic_array_internal.odin deleted file mode 100644 index 267ee0785..000000000 --- a/core/runtime/dynamic_array_internal.odin +++ /dev/null @@ -1,138 +0,0 @@ -package runtime - -__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 { - return true - } - - old_size := array.cap * elem_size - new_size := cap * elem_size - allocator := array.allocator - - new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) - if err != nil { - return false - } - if elem_size == 0 { - array.data = raw_data(new_data) - array.cap = cap - return true - } else if new_data != nil { - array.data = raw_data(new_data) - array.cap = min(cap, len(new_data)/elem_size) - return true - } - return false -} - -__dynamic_array_shrink :: proc(array_: rawptr, elem_size, elem_align: int, new_cap: int, loc := #caller_location) -> (did_shrink: 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 new_cap > array.cap { - return - } - - new_cap := new_cap - new_cap = max(new_cap, 0) - old_size := array.cap * elem_size - new_size := new_cap * elem_size - allocator := array.allocator - - new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) - if err != nil { - return - } - - array.data = raw_data(new_data) - array.len = min(new_cap, array.len) - array.cap = new_cap - return true -} - -__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 { - 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 { - return 0 - } - if item_count <= 0 { - 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 { - 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 { - 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 -} diff --git a/core/runtime/dynamic_map_internal.odin b/core/runtime/dynamic_map_internal.odin deleted file mode 100644 index 491a7974d..000000000 --- a/core/runtime/dynamic_map_internal.odin +++ /dev/null @@ -1,924 +0,0 @@ -package runtime - -import "core:intrinsics" -_ :: intrinsics - -// High performance, cache-friendly, open-addressed Robin Hood hashing hash map -// data structure with various optimizations for Odin. -// -// Copyright 2022 (c) Dale Weiler -// -// The core of the hash map data structure is the Raw_Map struct which is a -// type-erased representation of the map. This type-erased representation is -// used in two ways: static and dynamic. When static type information is known, -// the procedures suffixed with _static should be used instead of _dynamic. The -// static procedures are optimized since they have type information. Hashing of -// keys, comparison of keys, and data lookup are all optimized. When type -// information is not known, the procedures suffixed with _dynamic should be -// used. The representation of the map is the same for both static and dynamic, -// and procedures of each can be mixed and matched. The purpose of the dynamic -// representation is to enable reflection and runtime manipulation of the map. -// The dynamic procedures all take an additional Map_Info structure parameter -// which carries runtime values describing the size, alignment, and offset of -// various traits of a given key and value type pair. The Map_Info value can -// be created by calling map_info(K, V) with the key and value typeids. -// -// This map implementation makes extensive use of uintptr for representing -// sizes, lengths, capacities, masks, pointers, offsets, and addresses to avoid -// expensive sign extension and masking that would be generated if types were -// casted all over. The only place regular ints show up is in the cap() and -// len() implementations. -// -// To make this map cache-friendly it uses a novel strategy to ensure keys and -// values of the map are always cache-line aligned and that no single key or -// value of any type ever straddles a cache-line. This cache efficiency makes -// for quick lookups because the linear-probe always addresses data in a cache -// friendly way. This is enabled through the use of a special meta-type called -// a Map_Cell which packs as many values of a given type into a local array adding -// internal padding to round to MAP_CACHE_LINE_SIZE. One other benefit to storing -// the internal data in this manner is false sharing no longer occurs when using -// a map, enabling efficient concurrent access of the map data structure with -// minimal locking if desired. - -// With Robin Hood hashing a maximum load factor of 75% is ideal. -MAP_LOAD_FACTOR :: 75 - -// Minimum log2 capacity. -MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements - -// Has to be less than 100% though. -#assert(MAP_LOAD_FACTOR < 100) - -// This is safe to change. The log2 size of a cache-line. At minimum it has to -// be six though. Higher cache line sizes are permitted. -MAP_CACHE_LINE_LOG2 :: 6 - -// The size of a cache-line. -MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2 - -// The minimum cache-line size allowed by this implementation is 64 bytes since -// we need 6 bits in the base pointer to store the integer log2 capacity, which -// at maximum is 63. Odin uses signed integers to represent length and capacity, -// so only 63 bits are needed in the maximum case. -#assert(MAP_CACHE_LINE_SIZE >= 64) - -// Map_Cell type that packs multiple T in such a way to ensure that each T stays -// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0 -// -// This means a value of type T will never straddle a cache-line. -// -// When multiple Ts can fit in a single cache-line the data array will have more -// than one element. When it cannot, the data array will have one element and -// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE. -// -// We rely on the type system to do all the arithmetic and padding for us here. -// -// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit -// more involved as there now may be internal padding. The indexing now becomes -// -// N :: len(Map_Cell(T){}.data) -// i := index / N -// j := index % N -// cell[i].data[j] -// -// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some -// optimizations we can do to eliminate the need for any divisions as N will -// be bounded by [1, 64). -// -// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated -// as a regular array of T, which is the case for hashes. -Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) { - data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T, -} - -// So we can operate on a cell data structure at runtime without any type -// information, we have a simple table that stores some traits about the cell. -// -// 32-bytes on 64-bit -// 16-bytes on 32-bit -Map_Cell_Info :: struct { - size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits - align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits - size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits - elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits -} - -// map_cell_info :: proc "contextless" ($T: typeid) -> ^Map_Cell_Info {...} -map_cell_info :: intrinsics.type_map_cell_info - -// Same as the above procedure but at runtime with the cell Map_Cell_Info value. -@(require_results) -map_cell_index_dynamic :: #force_inline proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, index: uintptr) -> uintptr { - // Micro-optimize the common cases to save on integer division. - elements_per_cell := uintptr(info.elements_per_cell) - size_of_cell := uintptr(info.size_of_cell) - switch elements_per_cell { - case 1: - return base + (index * size_of_cell) - case 2: - cell_index := index >> 1 - data_index := index & 1 - size_of_type := uintptr(info.size_of_type) - return base + (cell_index * size_of_cell) + (data_index * size_of_type) - case: - cell_index := index / elements_per_cell - data_index := index % elements_per_cell - size_of_type := uintptr(info.size_of_type) - return base + (cell_index * size_of_cell) + (data_index * size_of_type) - } -} - -// Same as above procedure but with compile-time constant index. -@(require_results) -map_cell_index_dynamic_const :: proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, $INDEX: uintptr) -> uintptr { - elements_per_cell := uintptr(info.elements_per_cell) - size_of_cell := uintptr(info.size_of_cell) - size_of_type := uintptr(info.size_of_type) - cell_index := INDEX / elements_per_cell - data_index := INDEX % elements_per_cell - return base + (cell_index * size_of_cell) + (data_index * size_of_type) -} - -// We always round the capacity to a power of two so this becomes [16]Foo, which -// works out to [4]Cell(Foo). -// -// The following compile-time procedure indexes such a [N]Cell(T) structure as -// if it were a flat array accounting for the internal padding introduced by the -// Cell structure. -@(require_results) -map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T), index: uintptr) -> ^T #no_bounds_check { - N :: size_of(Map_Cell(T){}.data) / size_of(T) when size_of(T) > 0 else 1 - - #assert(N <= MAP_CACHE_LINE_SIZE) - - when size_of(Map_Cell(T)) == size_of([N]T) { - // No padding case, can treat as a regular array of []T. - - return &([^]T)(cells)[index] - } else when (N & (N - 1)) == 0 && N <= 8*size_of(uintptr) { - // Likely case, N is a power of two because T is a power of two. - - // Compute the integer log 2 of N, this is the shift amount to index the - // correct cell. Odin's intrinsics.count_leading_zeros does not produce a - // constant, hence this approach. We only need to check up to N = 64. - SHIFT :: 1 when N < 2 else - 2 when N < 4 else - 3 when N < 8 else - 4 when N < 16 else - 5 when N < 32 else 6 - #assert(SHIFT <= MAP_CACHE_LINE_LOG2) - // Unique case, no need to index data here since only one element. - when N == 1 { - return &cells[index >> SHIFT].data[0] - } else { - return &cells[index >> SHIFT].data[index & (N - 1)] - } - } else { - // Least likely (and worst case), we pay for a division operation but we - // assume the compiler does not actually generate a division. N will be in the - // range [1, CACHE_LINE_SIZE) and not a power of two. - return &cells[index / N].data[index % N] - } -} - -// len() for map -@(require_results) -map_len :: #force_inline proc "contextless" (m: Raw_Map) -> int { - return int(m.len) -} - -// cap() for map -@(require_results) -map_cap :: #force_inline proc "contextless" (m: Raw_Map) -> int { - // The data uintptr stores the capacity in the lower six bits which gives the - // a maximum value of 2^6-1, or 63. We store the integer log2 of capacity - // since our capacity is always a power of two. We only need 63 bits as Odin - // represents length and capacity as a signed integer. - return 0 if m.data == 0 else 1 << map_log2_cap(m) -} - -// Query the load factor of the map. This is not actually configurable, but -// some math is needed to compute it. Compute it as a fixed point percentage to -// avoid floating point operations. This division can be optimized out by -// multiplying by the multiplicative inverse of 100. -@(require_results) -map_load_factor :: #force_inline proc "contextless" (log2_capacity: uintptr) -> uintptr { - return ((uintptr(1) << log2_capacity) * MAP_LOAD_FACTOR) / 100 -} - -@(require_results) -map_resize_threshold :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return map_load_factor(map_log2_cap(m)) -} - -// The data stores the log2 capacity in the lower six bits. This is primarily -// used in the implementation rather than map_cap since the check for data = 0 -// isn't necessary in the implementation. cap() on the otherhand needs to work -// when called on an empty map. -@(require_results) -map_log2_cap :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return m.data & (64 - 1) -} - -// Canonicalize the data by removing the tagged capacity stored in the lower six -// bits of the data uintptr. -@(require_results) -map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return m.data &~ uintptr(64 - 1) -} - - -Map_Hash :: uintptr - -TOMBSTONE_MASK :: 1<<(size_of(Map_Hash)*8 - 1) - -// Procedure to check if a slot is empty for a given hash. This is represented -// by the zero value to make the zero value useful. This is a procedure just -// for prose reasons. -@(require_results) -map_hash_is_empty :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { - return hash == 0 -} - -@(require_results) -map_hash_is_deleted :: #force_no_inline proc "contextless" (hash: Map_Hash) -> bool { - // The MSB indicates a tombstone - return hash & TOMBSTONE_MASK != 0 -} -@(require_results) -map_hash_is_valid :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { - // The MSB indicates a tombstone - return (hash != 0) & (hash & TOMBSTONE_MASK == 0) -} - -@(require_results) -map_seed :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return map_seed_from_map_data(map_data(m)) -} - -// splitmix for uintptr -@(require_results) -map_seed_from_map_data :: #force_inline proc "contextless" (data: uintptr) -> uintptr { - when size_of(uintptr) == size_of(u64) { - mix := data + 0x9e3779b97f4a7c15 - mix = (mix ~ (mix >> 30)) * 0xbf58476d1ce4e5b9 - mix = (mix ~ (mix >> 27)) * 0x94d049bb133111eb - return mix ~ (mix >> 31) - } else { - mix := data + 0x9e3779b9 - mix = (mix ~ (mix >> 16)) * 0x21f0aaad - mix = (mix ~ (mix >> 15)) * 0x735a2d97 - return mix ~ (mix >> 15) - } -} - -// Computes the desired position in the array. This is just index % capacity, -// but a procedure as there's some math involved here to recover the capacity. -@(require_results) -map_desired_position :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash) -> uintptr { - // We do not use map_cap since we know the capacity will not be zero here. - capacity := uintptr(1) << map_log2_cap(m) - return uintptr(hash & Map_Hash(capacity - 1)) -} - -@(require_results) -map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash, slot: uintptr) -> uintptr { - // We do not use map_cap since we know the capacity will not be zero here. - capacity := uintptr(1) << map_log2_cap(m) - return (slot + capacity - map_desired_position(m, hash)) & (capacity - 1) -} - -// When working with the type-erased structure at runtime we need information -// about the map to make working with it possible. This info structure stores -// that. -// -// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be -// modified after creation -// -// 32-bytes on 64-bit -// 16-bytes on 32-bit -Map_Info :: struct { - ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit - vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit - key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit - key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit -} - - -// The Map_Info structure is basically a pseudo-table of information for a given K and V pair. -// map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...} -map_info :: intrinsics.type_map_info - -@(require_results) -map_kvh_data_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (ks: uintptr, vs: uintptr, hs: [^]Map_Hash, sk: uintptr, sv: uintptr) { - INFO_HS := intrinsics.type_map_cell_info(Map_Hash) - - capacity := uintptr(1) << map_log2_cap(m) - ks = map_data(m) - vs = map_cell_index_dynamic(ks, info.ks, capacity) // Skip past ks to get start of vs - hs_ := map_cell_index_dynamic(vs, info.vs, capacity) // Skip past vs to get start of hs - sk = map_cell_index_dynamic(hs_, INFO_HS, capacity) // Skip past hs to get start of sk - // Need to skip past two elements in the scratch key space to get to the start - // of the scratch value space, of which there's only two elements as well. - sv = map_cell_index_dynamic_const(sk, info.ks, 2) - - hs = ([^]Map_Hash)(hs_) - return -} - -@(require_results) -map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (vs: uintptr) { - capacity := uintptr(1) << map_log2_cap(m) - return map_cell_index_dynamic(map_data(m), info.ks, capacity) // Skip past ks to get start of vs -} - - -@(private, require_results) -map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr { - round :: #force_inline proc "contextless" (value: uintptr) -> uintptr { - CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 - return (value + CACHE_MASK) &~ CACHE_MASK - } - INFO_HS := intrinsics.type_map_cell_info(Map_Hash) - - size := uintptr(0) - size = round(map_cell_index_dynamic(size, info.ks, capacity)) - size = round(map_cell_index_dynamic(size, info.vs, capacity)) - size = round(map_cell_index_dynamic(size, INFO_HS, capacity)) - size = round(map_cell_index_dynamic(size, info.ks, 2)) // Two additional ks for scratch storage - size = round(map_cell_index_dynamic(size, info.vs, 2)) // Two additional vs for scratch storage - return size -} - -// The only procedure which needs access to the context is the one which allocates the map. -@(require_results) -map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) { - result.allocator = allocator // set the allocator always - if log2_capacity == 0 { - return - } - - if log2_capacity >= 64 { - // Overflowed, would be caused by log2_capacity > 64 - return {}, .Out_Of_Memory - } - - capacity := uintptr(1) << max(log2_capacity, MAP_MIN_LOG2_CAPACITY) - - CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 - - size := map_total_allocation_size(capacity, info) - - data := mem_alloc_non_zeroed(int(size), MAP_CACHE_LINE_SIZE, allocator, loc) or_return - data_ptr := uintptr(raw_data(data)) - if data_ptr == 0 { - err = .Out_Of_Memory - return - } - if intrinsics.expect(data_ptr & CACHE_MASK != 0, false) { - panic("allocation not aligned to a cache line", loc) - } else { - result.data = data_ptr | log2_capacity // Tagged pointer representation for capacity. - result.len = 0 - - map_clear_dynamic(&result, info) - } - return -} - -// This procedure has to stack allocate storage to store local keys during the -// Robin Hood hashing technique where elements are swapped in the backing -// arrays to reduce variance. This swapping can only be done with memcpy since -// there is no type information. -// -// This procedure returns the address of the just inserted value. -@(require_results) -map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { - h := h - pos := map_desired_position(m^, h) - distance := uintptr(0) - mask := (uintptr(1) << map_log2_cap(m^)) - 1 - - ks, vs, hs, sk, sv := map_kvh_data_dynamic(m^, info) - - // Avoid redundant loads of these values - size_of_k := info.ks.size_of_type - size_of_v := info.vs.size_of_type - - k := map_cell_index_dynamic(sk, info.ks, 0) - v := map_cell_index_dynamic(sv, info.vs, 0) - intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(ik), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(iv), size_of_v) - - // Temporary k and v dynamic storage for swap below - tk := map_cell_index_dynamic(sk, info.ks, 1) - tv := map_cell_index_dynamic(sv, info.vs, 1) - - swap_loop: for { - element_hash := hs[pos] - - if map_hash_is_empty(element_hash) { - k_dst := map_cell_index_dynamic(ks, info.ks, pos) - v_dst := map_cell_index_dynamic(vs, info.vs, pos) - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - return result if result != 0 else v_dst - } - - if map_hash_is_deleted(element_hash) { - break swap_loop - } - - if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance { - if result == 0 { - result = map_cell_index_dynamic(vs, info.vs, pos) - } - - kp := map_cell_index_dynamic(ks, info.ks, pos) - vp := map_cell_index_dynamic(vs, info.vs, pos) - - intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k) - - intrinsics.mem_copy_non_overlapping(rawptr(tv), rawptr(v), size_of_v) - intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(vp), size_of_v) - intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(tv), size_of_v) - - th := h - h = hs[pos] - hs[pos] = th - - distance = probe_distance - } - - pos = (pos + 1) & mask - distance += 1 - } - - // backward shift loop - hs[pos] = 0 - look_ahead: uintptr = 1 - for { - la_pos := (pos + look_ahead) & mask - element_hash := hs[la_pos] - - if map_hash_is_deleted(element_hash) { - look_ahead += 1 - hs[la_pos] = 0 - continue - } - - k_dst := map_cell_index_dynamic(ks, info.ks, pos) - v_dst := map_cell_index_dynamic(vs, info.vs, pos) - - if map_hash_is_empty(element_hash) { - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - return result if result != 0 else v_dst - } - - k_src := map_cell_index_dynamic(ks, info.ks, la_pos) - v_src := map_cell_index_dynamic(vs, info.vs, la_pos) - probe_distance := map_probe_distance(m^, element_hash, la_pos) - - if probe_distance < look_ahead { - // probed can be made ideal while placing saved (ending condition) - if result == 0 { - result = v_dst - } - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - // This will be an ideal move - pos = (la_pos - probe_distance) & mask - look_ahead -= probe_distance - - // shift until we hit ideal/empty - for probe_distance != 0 { - k_dst = map_cell_index_dynamic(ks, info.ks, pos) - v_dst = map_cell_index_dynamic(vs, info.vs, pos) - - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) - hs[pos] = element_hash - hs[la_pos] = 0 - - pos = (pos + 1) & mask - la_pos = (la_pos + 1) & mask - look_ahead = (la_pos - pos) & mask - element_hash = hs[la_pos] - if map_hash_is_empty(element_hash) { - return - } - - probe_distance = map_probe_distance(m^, element_hash, la_pos) - if probe_distance == 0 { - return - } - // can be ideal? - if probe_distance < look_ahead { - pos = (la_pos - probe_distance) & mask - } - k_src = map_cell_index_dynamic(ks, info.ks, la_pos) - v_src = map_cell_index_dynamic(vs, info.vs, la_pos) - } - return - } else if distance < probe_distance - look_ahead { - // shift back probed - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) - hs[pos] = element_hash - hs[la_pos] = 0 - } else { - // place saved, save probed - if result == 0 { - result = v_dst - } - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(k_src), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(v_src), size_of_v) - h = hs[la_pos] - hs[la_pos] = 0 - distance = probe_distance - look_ahead - } - - pos = (pos + 1) & mask - distance += 1 - } -} - -@(require_results) -map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { - log2_capacity := map_log2_cap(m^) - new_capacity := uintptr(1) << max(log2_capacity + 1, MAP_MIN_LOG2_CAPACITY) - return map_reserve_dynamic(m, info, new_capacity, loc) -} - - -@(require_results) -map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { - @(require_results) - ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { - z := intrinsics.count_leading_zeros(x) - if z > 0 && x & (x-1) != 0 { - z -= 1 - } - return size_of(uintptr)*8 - 1 - z - } - - if m.allocator.procedure == nil { - m.allocator = context.allocator - } - - new_capacity := new_capacity - old_capacity := uintptr(map_cap(m^)) - - if old_capacity >= new_capacity { - return nil - } - - // ceiling nearest power of two - log2_new_capacity := ceil_log2(new_capacity) - - log2_min_cap := max(MAP_MIN_LOG2_CAPACITY, log2_new_capacity) - - if m.data == 0 { - m^ = map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return - return nil - } - - resized := map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return - - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - - // Cache these loads to avoid hitting them in the for loop. - n := m.len - for i in 0.. (did_shrink: bool, err: Allocator_Error) { - if m.allocator.procedure == nil { - m.allocator = context.allocator - } - - // Cannot shrink the capacity if the number of items in the map would exceed - // one minus the current log2 capacity's resize threshold. That is the shrunk - // map needs to be within the max load factor. - log2_capacity := map_log2_cap(m^) - if uintptr(m.len) >= map_load_factor(log2_capacity - 1) { - return false, nil - } - - shrunk := map_alloc_dynamic(info, log2_capacity - 1, m.allocator) or_return - - capacity := uintptr(1) << log2_capacity - - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - - n := m.len - for i in 0.. Allocator_Error { - ptr := rawptr(map_data(m)) - size := int(map_total_allocation_size(uintptr(map_cap(m)), info)) - err := mem_free_with_size(ptr, size, m.allocator, loc) - #partial switch err { - case .None, .Mode_Not_Implemented: - return nil - } - return err -} - -@(require_results) -map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { - if map_len(m) == 0 { - return 0, false - } - h := info.key_hasher(rawptr(k), map_seed(m)) - p := map_desired_position(m, h) - d := uintptr(0) - c := (uintptr(1) << map_log2_cap(m)) - 1 - ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) - for { - element_hash := hs[p] - if map_hash_is_empty(element_hash) { - return 0, false - } else if d > map_probe_distance(m, element_hash, p) { - return 0, false - } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { - return p, true - } - p = (p + 1) & c - d += 1 - } -} -@(require_results) -map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { - if map_len(m) == 0 { - return false - } - h := info.key_hasher(rawptr(k), map_seed(m)) - p := map_desired_position(m, h) - d := uintptr(0) - c := (uintptr(1) << map_log2_cap(m)) - 1 - ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) - for { - element_hash := hs[p] - if map_hash_is_empty(element_hash) { - return false - } else if d > map_probe_distance(m, element_hash, p) { - return false - } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { - return true - } - p = (p + 1) & c - d += 1 - } -} - - - -@(require_results) -map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { - index := map_lookup_dynamic(m^, info, k) or_return - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - hs[index] |= TOMBSTONE_MASK - old_k = map_cell_index_dynamic(ks, info.ks, index) - old_v = map_cell_index_dynamic(vs, info.vs, index) - m.len -= 1 - ok = true - - mask := (uintptr(1)< (ks: [^]Map_Cell(K), vs: [^]Map_Cell(V), hs: [^]Map_Hash) { - capacity := uintptr(cap(m)) - ks = ([^]Map_Cell(K))(map_data(transmute(Raw_Map)m)) - vs = ([^]Map_Cell(V))(map_cell_index_static(ks, capacity)) - hs = ([^]Map_Hash)(map_cell_index_static(vs, capacity)) - return -} - - -@(require_results) -map_get :: proc "contextless" (m: $T/map[$K]$V, key: K) -> (stored_key: K, stored_value: V, ok: bool) { - rm := transmute(Raw_Map)m - if rm.len == 0 { - return - } - info := intrinsics.type_map_info(T) - key := key - - h := info.key_hasher(&key, map_seed(rm)) - pos := map_desired_position(rm, h) - distance := uintptr(0) - mask := (uintptr(1) << map_log2_cap(rm)) - 1 - ks, vs, hs := map_kvh_data_static(m) - for { - element_hash := hs[pos] - if map_hash_is_empty(element_hash) { - return - } else if distance > map_probe_distance(rm, element_hash, pos) { - return - } else if element_hash == h { - element_key := map_cell_index_static(ks, pos) - if info.key_equal(&key, rawptr(element_key)) { - element_value := map_cell_index_static(vs, pos) - stored_key = (^K)(element_key)^ - stored_value = (^V)(element_value)^ - ok = true - return - } - - } - pos = (pos + 1) & mask - distance += 1 - } -} - -// IMPORTANT: USED WITHIN THE COMPILER -__dynamic_map_get :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, key: rawptr) -> (ptr: rawptr) { - if m.len == 0 { - return nil - } - pos := map_desired_position(m^, h) - distance := uintptr(0) - mask := (uintptr(1) << map_log2_cap(m^)) - 1 - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - for { - element_hash := hs[pos] - if map_hash_is_empty(element_hash) { - return nil - } else if distance > map_probe_distance(m^, element_hash, pos) { - return nil - } else if element_hash == h && info.key_equal(key, rawptr(map_cell_index_dynamic(ks, info.ks, pos))) { - return rawptr(map_cell_index_dynamic(vs, info.vs, pos)) - } - pos = (pos + 1) & mask - distance += 1 - } -} - -// IMPORTANT: USED WITHIN THE COMPILER -__dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) { - if m.len >= map_resize_threshold(m^) { - return map_grow_dynamic(m, info, loc), true - } - return nil, false -} - -__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> rawptr { - return __dynamic_map_set(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc) -} - - -// IMPORTANT: USED WITHIN THE COMPILER -__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> rawptr { - if found := __dynamic_map_get(m, info, hash, key); found != nil { - intrinsics.mem_copy_non_overlapping(found, value, info.vs.size_of_type) - return found - } - - hash := hash - err, has_grown := __dynamic_map_check_grow(m, info, loc) - if err != nil { - return nil - } - if has_grown { - hash = info.key_hasher(key, map_seed(m^)) - } - - result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value)) - m.len += 1 - return rawptr(result) -} - -// IMPORTANT: USED WITHIN THE COMPILER -@(private) -__dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error { - return map_reserve_dynamic(m, info, uintptr(new_capacity), loc) -} - - - -// NOTE: the default hashing algorithm derives from fnv64a, with some minor modifications to work for `map` type: -// -// * Convert a `0` result to `1` -// * "empty entry" -// * Prevent the top bit from being set -// * "deleted entry" -// -// Both of these modification are necessary for the implementation of the `map` - -INITIAL_HASH_SEED :: 0xcbf29ce484222325 - -HASH_MASK :: 1 << (8*size_of(uintptr) - 1) -1 - -default_hasher :: #force_inline proc "contextless" (data: rawptr, seed: uintptr, N: int) -> uintptr { - h := u64(seed) + INITIAL_HASH_SEED - p := ([^]byte)(data) - for _ in 0.. uintptr { - str := (^[]byte)(data) - return default_hasher(raw_data(str^), seed, len(str)) -} -default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr { - h := u64(seed) + INITIAL_HASH_SEED - if ptr := (^[^]byte)(data)^; ptr != nil { - for ptr[0] != 0 { - h = (h ~ u64(ptr[0])) * 0x100000001b3 - ptr = ptr[1:] - } - } - h &= HASH_MASK - return uintptr(h) | uintptr(uintptr(h) == 0) -} diff --git a/core/runtime/entry_unix.odin b/core/runtime/entry_unix.odin deleted file mode 100644 index f494a509e..000000000 --- a/core/runtime/entry_unix.odin +++ /dev/null @@ -1,59 +0,0 @@ -//+private -//+build linux, darwin, freebsd, openbsd -//+no-instrumentation -package runtime - -import "core:intrinsics" - -when ODIN_BUILD_MODE == .Dynamic { - @(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/) - _odin_entry_point :: proc "c" () { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - } - @(link_name="_odin_exit_point", linkage="strong", require/*, link_section=".fini"*/) - _odin_exit_point :: proc "c" () { - context = default_context() - #force_no_inline _cleanup_runtime() - } - @(link_name="main", linkage="strong", require) - main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { - return 0 - } -} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - when ODIN_NO_CRT { - // NOTE(flysand): We need to start from assembly because we need - // to retrieve argc and argv from the stack - when ODIN_ARCH == .amd64 { - @require foreign import entry "entry_unix_no_crt_amd64.asm" - SYS_exit :: 60 - } else when ODIN_ARCH == .i386 { - @require foreign import entry "entry_unix_no_crt_i386.asm" - SYS_exit :: 1 - } else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { - @require foreign import entry "entry_unix_no_crt_darwin_arm64.asm" - SYS_exit :: 1 - } - @(link_name="_start_odin", linkage="strong", require) - _start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! { - args__ = argv[:argc] - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - intrinsics.syscall(SYS_exit, 0) - unreachable() - } - } else { - @(link_name="main", linkage="strong", require) - main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { - args__ = argv[:argc] - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - return 0 - } - } -} diff --git a/core/runtime/entry_unix_no_crt_amd64.asm b/core/runtime/entry_unix_no_crt_amd64.asm deleted file mode 100644 index f0bdce8d7..000000000 --- a/core/runtime/entry_unix_no_crt_amd64.asm +++ /dev/null @@ -1,43 +0,0 @@ -bits 64 - -extern _start_odin -global _start - -section .text - -;; Entry point for programs that specify -no-crt option -;; This entry point should be compatible with dynamic loaders on linux -;; The parameters the dynamic loader passes to the _start function: -;; RDX = pointer to atexit function -;; The stack layout is as follows: -;; +-------------------+ -;; NULL -;; +-------------------+ -;; envp[m] -;; +-------------------+ -;; ... -;; +-------------------+ -;; envp[0] -;; +-------------------+ -;; NULL -;; +-------------------+ -;; argv[n] -;; +-------------------+ -;; ... -;; +-------------------+ -;; argv[0] -;; +-------------------+ -;; argc -;; +-------------------+ <------ RSP -;; -_start: - ;; Mark stack frame as the top of the stack - xor rbp, rbp - ;; Load argc into 1st param reg, argv into 2nd param reg - pop rdi - mov rdx, rsi - ;; Align stack pointer down to 16-bytes (sysv calling convention) - and rsp, -16 - ;; Call into odin entry point - call _start_odin - jmp $$ \ No newline at end of file diff --git a/core/runtime/entry_unix_no_crt_darwin_arm64.asm b/core/runtime/entry_unix_no_crt_darwin_arm64.asm deleted file mode 100644 index 0f71fbdf8..000000000 --- a/core/runtime/entry_unix_no_crt_darwin_arm64.asm +++ /dev/null @@ -1,20 +0,0 @@ - .section __TEXT,__text - - ; NOTE(laytan): this should ideally be the -minimum-os-version flag but there is no nice way of preprocessing assembly in Odin. - ; 10 seems to be the lowest it goes and I don't see it mess with any targeted os version so this seems fine. - .build_version macos, 10, 0 - - .extern __start_odin - - .global _main - .align 2 -_main: - mov x5, sp ; use x5 as the stack pointer - - str x0, [x5] ; get argc into x0 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) - str x1, [x5, #8] ; get argv into x1 - - and sp, x5, #~15 ; force 16-byte alignment of the stack - - bl __start_odin ; call into Odin entry point - ret ; should never get here diff --git a/core/runtime/entry_unix_no_crt_i386.asm b/core/runtime/entry_unix_no_crt_i386.asm deleted file mode 100644 index a61d56a16..000000000 --- a/core/runtime/entry_unix_no_crt_i386.asm +++ /dev/null @@ -1,18 +0,0 @@ -bits 32 - -extern _start_odin -global _start - -section .text - -;; NOTE(flysand): For description see the corresponding *_amd64.asm file -;; also I didn't test this on x86-32 -_start: - xor ebp, rbp - pop ecx - mov eax, esp - and esp, -16 - push eax - push ecx - call _start_odin - jmp $$ \ No newline at end of file diff --git a/core/runtime/entry_wasm.odin b/core/runtime/entry_wasm.odin deleted file mode 100644 index e7f3f156f..000000000 --- a/core/runtime/entry_wasm.odin +++ /dev/null @@ -1,20 +0,0 @@ -//+private -//+build wasm32, wasm64p32 -//+no-instrumentation -package runtime - -import "core:intrinsics" - -when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - @(link_name="_start", linkage="strong", require, export) - _start :: proc "c" () { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - } - @(link_name="_end", linkage="strong", require, export) - _end :: proc "c" () { - context = default_context() - #force_no_inline _cleanup_runtime() - } -} \ No newline at end of file diff --git a/core/runtime/entry_windows.odin b/core/runtime/entry_windows.odin deleted file mode 100644 index b6fbe1dcc..000000000 --- a/core/runtime/entry_windows.odin +++ /dev/null @@ -1,50 +0,0 @@ -//+private -//+build windows -//+no-instrumentation -package runtime - -import "core:intrinsics" - -when ODIN_BUILD_MODE == .Dynamic { - @(link_name="DllMain", linkage="strong", require) - DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { - context = default_context() - - // Populate Windows DLL-specific global - dll_forward_reason = DLL_Forward_Reason(fdwReason) - - switch dll_forward_reason { - case .Process_Attach: - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - case .Process_Detach: - #force_no_inline _cleanup_runtime() - case .Thread_Attach: - break - case .Thread_Detach: - break - } - return true - } -} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - when ODIN_ARCH == .i386 || ODIN_NO_CRT { - @(link_name="mainCRTStartup", linkage="strong", require) - mainCRTStartup :: proc "system" () -> i32 { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - return 0 - } - } else { - @(link_name="main", linkage="strong", require) - main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { - args__ = argv[:argc] - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - return 0 - } - } -} \ No newline at end of file diff --git a/core/runtime/error_checks.odin b/core/runtime/error_checks.odin deleted file mode 100644 index ea6333c29..000000000 --- a/core/runtime/error_checks.odin +++ /dev/null @@ -1,292 +0,0 @@ -package runtime - -@(no_instrumentation) -bounds_trap :: proc "contextless" () -> ! { - when ODIN_OS == .Windows { - windows_trap_array_bounds() - } else { - trap() - } -} - -@(no_instrumentation) -type_assertion_trap :: proc "contextless" () -> ! { - when ODIN_OS == .Windows { - windows_trap_type_assertion() - } else { - trap() - } -} - - -bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) { - if uint(index) < uint(count) { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Index ") - print_i64(i64(index)) - print_string(" is out of range 0..<") - print_i64(i64(count)) - print_byte('\n') - bounds_trap() - } - handle_error(file, line, column, index, count) -} - -@(no_instrumentation) -slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid slice indices ") - print_i64(i64(lo)) - print_string(":") - print_i64(i64(hi)) - print_string(" is out of range 0..<") - print_i64(i64(len)) - print_byte('\n') - bounds_trap() -} - -@(no_instrumentation) -multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid slice indices ") - print_i64(i64(lo)) - print_string(":") - print_i64(i64(hi)) - print_byte('\n') - bounds_trap() -} - - -multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) { - if lo <= hi { - return - } - multi_pointer_slice_handle_error(file, line, column, lo, hi) -} - -slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) { - if 0 <= hi && hi <= len { - return - } - slice_handle_error(file, line, column, 0, hi, len) -} - -slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) { - if 0 <= lo && lo <= len && lo <= hi && hi <= len { - return - } - slice_handle_error(file, line, column, lo, hi, len) -} - -dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) { - if 0 <= low && low <= high && high <= max { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid dynamic array indices ") - print_i64(i64(low)) - print_string(":") - print_i64(i64(high)) - print_string(" is out of range 0..<") - print_i64(i64(max)) - print_byte('\n') - bounds_trap() - } - handle_error(file, line, column, low, high, max) -} - - -matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) { - if uint(row_index) < uint(row_count) && - uint(column_index) < uint(column_count) { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Matrix indices [") - print_i64(i64(row_index)) - print_string(", ") - print_i64(i64(column_index)) - print_string(" is out of range [0..<") - print_i64(i64(row_count)) - print_string(", 0..<") - print_i64(i64(column_count)) - print_string("]") - print_byte('\n') - bounds_trap() - } - handle_error(file, line, column, row_index, column_index, row_count, column_count) -} - - -when ODIN_NO_RTTI { - type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32) { - if ok { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion\n") - type_assertion_trap() - } - handle_error(file, line, column) - } - - type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32) { - if ok { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion\n") - type_assertion_trap() - } - handle_error(file, line, column) - } -} else { - type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) { - if ok { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion from ") - print_typeid(from) - print_string(" to ") - print_typeid(to) - print_byte('\n') - type_assertion_trap() - } - handle_error(file, line, column, from, to) - } - - type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) { - if ok { - return - } - - variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid { - if id == nil || data == nil { - return id - } - ti := type_info_base(type_info_of(id)) - #partial switch v in ti.variant { - case Type_Info_Any: - return (^any)(data).id - case Type_Info_Union: - tag_ptr := uintptr(data) + v.tag_offset - idx := 0 - switch v.tag_type.size { - case 1: idx = int((^u8)(tag_ptr)^) - 1 - case 2: idx = int((^u16)(tag_ptr)^) - 1 - case 4: idx = int((^u32)(tag_ptr)^) - 1 - case 8: idx = int((^u64)(tag_ptr)^) - 1 - case 16: idx = int((^u128)(tag_ptr)^) - 1 - } - if idx < 0 { - return nil - } else if idx < len(v.variants) { - return v.variants[idx].id - } - } - return id - } - - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! { - - actual := variant_type(from, from_data) - - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion from ") - print_typeid(from) - print_string(" to ") - print_typeid(to) - if actual != from { - print_string(", actual type: ") - print_typeid(actual) - } - print_byte('\n') - type_assertion_trap() - } - handle_error(file, line, column, from, to, from_data) - } -} - - -make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) { - if 0 <= len { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! { - print_caller_location(loc) - print_string(" Invalid slice length for make: ") - print_i64(i64(len)) - print_byte('\n') - bounds_trap() - } - handle_error(loc, len) -} - -make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) { - if 0 <= len && len <= cap { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) -> ! { - print_caller_location(loc) - print_string(" Invalid dynamic array parameters for make: ") - print_i64(i64(len)) - print_byte(':') - print_i64(i64(cap)) - print_byte('\n') - bounds_trap() - } - handle_error(loc, len, cap) -} - -make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) { - if 0 <= cap { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) -> ! { - print_caller_location(loc) - print_string(" Invalid map capacity for make: ") - print_i64(i64(cap)) - print_byte('\n') - bounds_trap() - } - handle_error(loc, cap) -} - - - - - -bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) { - bounds_check_error(loc.file_path, loc.line, loc.column, index, count) -} - -slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) { - slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len) -} - -slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) { - slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len) -} - -dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) { - dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max) -} diff --git a/core/runtime/internal.odin b/core/runtime/internal.odin deleted file mode 100644 index a03c2a701..000000000 --- a/core/runtime/internal.odin +++ /dev/null @@ -1,1036 +0,0 @@ -package runtime - -import "core:intrinsics" - -@(private="file") -IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 - -@(private) -RUNTIME_LINKAGE :: "strong" when ( - (ODIN_USE_SEPARATE_MODULES || - ODIN_BUILD_MODE == .Dynamic || - !ODIN_NO_CRT) && - !IS_WASM) else "internal" -RUNTIME_REQUIRE :: !ODIN_TILDE - -@(private) -__float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16 - - -@(private) -byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check { - return ([^]byte)(data)[:max(len, 0)] -} - -is_power_of_two_int :: #force_inline proc(x: int) -> bool { - if x <= 0 { - return false - } - return (x & (x-1)) == 0 -} - -align_forward_int :: #force_inline proc(ptr, align: int) -> int { - assert(is_power_of_two_int(align)) - - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p -} - -is_power_of_two_uintptr :: #force_inline proc(x: uintptr) -> bool { - if x <= 0 { - return false - } - return (x & (x-1)) == 0 -} - -align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr { - assert(is_power_of_two_uintptr(align)) - - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p -} - -mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { - if data == nil { - return nil - } - if len <= 0 { - return data - } - intrinsics.mem_zero(data, len) - return data -} - -mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { - if src != nil && dst != src && len > 0 { - // NOTE(bill): This _must_ be implemented like C's memmove - intrinsics.mem_copy(dst, src, len) - } - return dst -} - -mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { - if src != nil && dst != src && len > 0 { - // NOTE(bill): This _must_ be implemented like C's memcpy - intrinsics.mem_copy_non_overlapping(dst, src, len) - } - return dst -} - -DEFAULT_ALIGNMENT :: 2*align_of(rawptr) - -mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 { - return nil, nil - } - if allocator.procedure == nil { - return nil, nil - } - return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) -} - -mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 || allocator.procedure == nil { - return nil, nil - } - return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) -} - -mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 || allocator.procedure == nil { - return nil, nil - } - return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, size, alignment, nil, 0, loc) -} - -mem_free :: #force_inline proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - if ptr == nil || allocator.procedure == nil { - return nil - } - _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, 0, loc) - return err -} - -mem_free_with_size :: #force_inline proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - if ptr == nil || allocator.procedure == nil { - return nil - } - _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, byte_count, loc) - return err -} - -mem_free_bytes :: #force_inline proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - if bytes == nil || allocator.procedure == nil { - return nil - } - _, err := allocator.procedure(allocator.data, .Free, 0, 0, raw_data(bytes), len(bytes), loc) - return err -} - - -mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { - if allocator.procedure != nil { - _, err = allocator.procedure(allocator.data, .Free_All, 0, 0, nil, 0, loc) - } - return -} - -_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - if allocator.procedure == nil { - return nil, nil - } - if new_size == 0 { - if ptr != nil { - _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) - return - } - return - } else if ptr == nil { - if should_zero { - return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) - } else { - return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) - } - } else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 { - data = ([^]byte)(ptr)[:old_size] - return - } - - if should_zero { - data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc) - } else { - data, err = allocator.procedure(allocator.data, .Resize_Non_Zeroed, new_size, alignment, ptr, old_size, loc) - } - if err == .Mode_Not_Implemented { - if should_zero { - data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) - } else { - data, err = allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) - } - if err != nil { - return - } - copy(data, ([^]byte)(ptr)[:old_size]) - _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) - } - return -} - -mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc) -} -non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) -} - -memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool { - switch { - case n == 0: return true - case x == y: return true - } - a, b := ([^]byte)(x), ([^]byte)(y) - length := uint(n) - - for i := uint(0); i < length; i += 1 { - if a[i] != b[i] { - return false - } - } - return true - -/* - - when size_of(uint) == 8 { - if word_length := length >> 3; word_length != 0 { - for _ in 0..> 2; word_length != 0 { - for _ in 0.. int #no_bounds_check { - switch { - case a == b: return 0 - case a == nil: return -1 - case b == nil: return +1 - } - - x := uintptr(a) - y := uintptr(b) - n := uintptr(n) - - SU :: size_of(uintptr) - fast := n/SU + 1 - offset := (fast-1)*SU - curr_block := uintptr(0) - if n < SU { - fast = 0 - } - - for /**/; curr_block < fast; curr_block += 1 { - va := (^uintptr)(x + curr_block * size_of(uintptr))^ - vb := (^uintptr)(y + curr_block * size_of(uintptr))^ - if va ~ vb != 0 { - for pos := curr_block*SU; pos < n; pos += 1 { - a := (^byte)(x+pos)^ - b := (^byte)(y+pos)^ - if a ~ b != 0 { - return -1 if (int(a) - int(b)) < 0 else +1 - } - } - } - } - - for /**/; offset < n; offset += 1 { - a := (^byte)(x+offset)^ - b := (^byte)(y+offset)^ - if a ~ b != 0 { - return -1 if (int(a) - int(b)) < 0 else +1 - } - } - - return 0 -} - -memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check { - x := uintptr(a) - n := uintptr(n) - - SU :: size_of(uintptr) - fast := n/SU + 1 - offset := (fast-1)*SU - curr_block := uintptr(0) - if n < SU { - fast = 0 - } - - for /**/; curr_block < fast; curr_block += 1 { - va := (^uintptr)(x + curr_block * size_of(uintptr))^ - if va ~ 0 != 0 { - for pos := curr_block*SU; pos < n; pos += 1 { - a := (^byte)(x+pos)^ - if a ~ 0 != 0 { - return -1 if int(a) < 0 else +1 - } - } - } - } - - for /**/; offset < n; offset += 1 { - a := (^byte)(x+offset)^ - if a ~ 0 != 0 { - return -1 if int(a) < 0 else +1 - } - } - - return 0 -} - -string_eq :: proc "contextless" (lhs, rhs: string) -> bool { - x := transmute(Raw_String)lhs - y := transmute(Raw_String)rhs - if x.len != y.len { - return false - } - return #force_inline memory_equal(x.data, y.data, x.len) -} - -string_cmp :: proc "contextless" (a, b: string) -> int { - x := transmute(Raw_String)a - y := transmute(Raw_String)b - - ret := memory_compare(x.data, y.data, min(x.len, y.len)) - if ret == 0 && x.len != y.len { - return -1 if x.len < y.len else +1 - } - return ret -} - -string_ne :: #force_inline proc "contextless" (a, b: string) -> bool { return !string_eq(a, b) } -string_lt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) < 0 } -string_gt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) > 0 } -string_le :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) <= 0 } -string_ge :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) >= 0 } - -cstring_len :: proc "contextless" (s: cstring) -> int { - p0 := uintptr((^byte)(s)) - p := p0 - for p != 0 && (^byte)(p)^ != 0 { - p += 1 - } - return int(p - p0) -} - -cstring_to_string :: proc "contextless" (s: cstring) -> string { - if s == nil { - return "" - } - ptr := (^byte)(s) - n := cstring_len(s) - return transmute(string)Raw_String{ptr, n} -} - - -cstring_eq :: proc "contextless" (lhs, rhs: cstring) -> bool { - x := ([^]byte)(lhs) - y := ([^]byte)(rhs) - if x == y { - return true - } - if (x == nil) ~ (y == nil) { - return false - } - xn := cstring_len(lhs) - yn := cstring_len(rhs) - if xn != yn { - return false - } - return #force_inline memory_equal(x, y, xn) -} - -cstring_cmp :: proc "contextless" (lhs, rhs: cstring) -> int { - x := ([^]byte)(lhs) - y := ([^]byte)(rhs) - if x == y { - return 0 - } - if (x == nil) ~ (y == nil) { - return -1 if x == nil else +1 - } - xn := cstring_len(lhs) - yn := cstring_len(rhs) - ret := memory_compare(x, y, min(xn, yn)) - if ret == 0 && xn != yn { - return -1 if xn < yn else +1 - } - return ret -} - -cstring_ne :: #force_inline proc "contextless" (a, b: cstring) -> bool { return !cstring_eq(a, b) } -cstring_lt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) < 0 } -cstring_gt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) > 0 } -cstring_le :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) <= 0 } -cstring_ge :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) >= 0 } - - -complex32_eq :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) == real(b) && imag(a) == imag(b) } -complex32_ne :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) != real(b) || imag(a) != imag(b) } - -complex64_eq :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) == real(b) && imag(a) == imag(b) } -complex64_ne :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) != real(b) || imag(a) != imag(b) } - -complex128_eq :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) == real(b) && imag(a) == imag(b) } -complex128_ne :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) != real(b) || imag(a) != imag(b) } - - -quaternion64_eq :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } -quaternion64_ne :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } - -quaternion128_eq :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } -quaternion128_ne :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } - -quaternion256_eq :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } -quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } - - -string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) { - // NOTE(bill): Duplicated here to remove dependency on package unicode/utf8 - - @static accept_sizes := [256]u8{ - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7f - - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8f - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9f - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xa0-0xaf - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xb0-0xbf - 0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xc0-0xcf - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xd0-0xdf - 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xe0-0xef - 0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xf0-0xff - } - Accept_Range :: struct {lo, hi: u8} - - @static accept_ranges := [5]Accept_Range{ - {0x80, 0xbf}, - {0xa0, 0xbf}, - {0x80, 0x9f}, - {0x90, 0xbf}, - {0x80, 0x8f}, - } - - MASKX :: 0b0011_1111 - MASK2 :: 0b0001_1111 - MASK3 :: 0b0000_1111 - MASK4 :: 0b0000_0111 - - LOCB :: 0b1000_0000 - HICB :: 0b1011_1111 - - - RUNE_ERROR :: '\ufffd' - - n := len(s) - if n < 1 { - return RUNE_ERROR, 0 - } - s0 := s[0] - x := accept_sizes[s0] - if x >= 0xF0 { - mask := rune(x) << 31 >> 31 // NOTE(bill): Create 0x0000 or 0xffff. - return rune(s[0])&~mask | RUNE_ERROR&mask, 1 - } - sz := x & 7 - accept := accept_ranges[x>>4] - if n < int(sz) { - return RUNE_ERROR, 1 - } - b1 := s[1] - if b1 < accept.lo || accept.hi < b1 { - return RUNE_ERROR, 1 - } - if sz == 2 { - return rune(s0&MASK2)<<6 | rune(b1&MASKX), 2 - } - b2 := s[2] - if b2 < LOCB || HICB < b2 { - return RUNE_ERROR, 1 - } - if sz == 3 { - return rune(s0&MASK3)<<12 | rune(b1&MASKX)<<6 | rune(b2&MASKX), 3 - } - b3 := s[3] - if b3 < LOCB || HICB < b3 { - return RUNE_ERROR, 1 - } - return rune(s0&MASK4)<<18 | rune(b1&MASKX)<<12 | rune(b2&MASKX)<<6 | rune(b3&MASKX), 4 -} - -string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) { - RUNE_ERROR :: '\ufffd' - RUNE_SELF :: 0x80 - UTF_MAX :: 4 - - r: rune - size: int - start, end, limit: int - - end = len(s) - if end == 0 { - return RUNE_ERROR, 0 - } - start = end-1 - r = rune(s[start]) - if r < RUNE_SELF { - return r, 1 - } - - limit = max(end - UTF_MAX, 0) - - for start-=1; start >= limit; start-=1 { - if (s[start] & 0xc0) != RUNE_SELF { - break - } - } - - start = max(start, 0) - r, size = string_decode_rune(s[start:end]) - if start+size != end { - return RUNE_ERROR, 1 - } - return r, size -} - -abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { - p, q := abs(real(x)), abs(imag(x)) - if p < q { - p, q = q, p - } - if p == 0 { - return 0 - } - q = q / p - return p * f16(intrinsics.sqrt(f32(1 + q*q))) -} -abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 { - p, q := abs(real(x)), abs(imag(x)) - if p < q { - p, q = q, p - } - if p == 0 { - return 0 - } - q = q / p - return p * intrinsics.sqrt(1 + q*q) -} -abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 { - p, q := abs(real(x)), abs(imag(x)) - if p < q { - p, q = q, p - } - if p == 0 { - return 0 - } - q = q / p - return p * intrinsics.sqrt(1 + q*q) -} -abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 { - r, i, j, k := real(x), imag(x), jmag(x), kmag(x) - return f16(intrinsics.sqrt(f32(r*r + i*i + j*j + k*k))) -} -abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 { - r, i, j, k := real(x), imag(x), jmag(x), kmag(x) - return intrinsics.sqrt(r*r + i*i + j*j + k*k) -} -abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 { - r, i, j, k := real(x), imag(x), jmag(x), kmag(x) - return intrinsics.sqrt(r*r + i*i + j*j + k*k) -} - - -quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 { - e, f: f16 - - if abs(real(m)) >= abs(imag(m)) { - ratio := imag(m) / real(m) - denom := real(m) + ratio*imag(m) - e = (real(n) + imag(n)*ratio) / denom - f = (imag(n) - real(n)*ratio) / denom - } else { - ratio := real(m) / imag(m) - denom := imag(m) + ratio*real(m) - e = (real(n)*ratio + imag(n)) / denom - f = (imag(n)*ratio - real(n)) / denom - } - - return complex(e, f) -} - - -quo_complex64 :: proc "contextless" (n, m: complex64) -> complex64 { - e, f: f32 - - if abs(real(m)) >= abs(imag(m)) { - ratio := imag(m) / real(m) - denom := real(m) + ratio*imag(m) - e = (real(n) + imag(n)*ratio) / denom - f = (imag(n) - real(n)*ratio) / denom - } else { - ratio := real(m) / imag(m) - denom := imag(m) + ratio*real(m) - e = (real(n)*ratio + imag(n)) / denom - f = (imag(n)*ratio - real(n)) / denom - } - - return complex(e, f) -} - -quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 { - e, f: f64 - - if abs(real(m)) >= abs(imag(m)) { - ratio := imag(m) / real(m) - denom := real(m) + ratio*imag(m) - e = (real(n) + imag(n)*ratio) / denom - f = (imag(n) - real(n)*ratio) / denom - } else { - ratio := real(m) / imag(m) - denom := imag(m) + ratio*real(m) - e = (real(n)*ratio + imag(n)) / denom - f = (imag(n)*ratio - real(n)) / denom - } - - return complex(e, f) -} - -mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 - t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 - t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 - t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 - t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 - t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 - t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 - t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 - t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 - t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) - - t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 - t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) - - t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 - t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) - - t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 - t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -@(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -truncsfhf2 :: proc "c" (value: f32) -> __float16 { - v: struct #raw_union { i: u32, f: f32 } - i, s, e, m: i32 - - v.f = value - i = i32(v.i) - - s = (i >> 16) & 0x00008000 - e = ((i >> 23) & 0x000000ff) - (127 - 15) - m = i & 0x007fffff - - - if e <= 0 { - if e < -10 { - return transmute(__float16)u16(s) - } - m = (m | 0x00800000) >> u32(1 - e) - - if m & 0x00001000 != 0 { - m += 0x00002000 - } - - return transmute(__float16)u16(s | (m >> 13)) - } else if e == 0xff - (127 - 15) { - if m == 0 { - return transmute(__float16)u16(s | 0x7c00) /* NOTE(bill): infinity */ - } else { - /* NOTE(bill): NAN */ - m >>= 13 - return transmute(__float16)u16(s | 0x7c00 | m | i32(m == 0)) - } - } else { - if m & 0x00001000 != 0 { - m += 0x00002000 - if (m & 0x00800000) != 0 { - m = 0 - e += 1 - } - } - - if e > 30 { - f := i64(1e12) - for j := 0; j < 10; j += 1 { - /* NOTE(bill): Cause overflow */ - g := intrinsics.volatile_load(&f) - g *= g - intrinsics.volatile_store(&f, g) - } - - return transmute(__float16)u16(s | 0x7c00) - } - - return transmute(__float16)u16(s | (e << 10) | (m >> 13)) - } -} - - -@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -truncdfhf2 :: proc "c" (value: f64) -> __float16 { - return truncsfhf2(f32(value)) -} - -@(link_name="__gnu_h2f_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -gnu_h2f_ieee :: proc "c" (value_: __float16) -> f32 { - fp32 :: struct #raw_union { u: u32, f: f32 } - - value := transmute(u16)value_ - v: fp32 - magic, inf_or_nan: fp32 - magic.u = u32((254 - 15) << 23) - inf_or_nan.u = u32((127 + 16) << 23) - - v.u = u32(value & 0x7fff) << 13 - v.f *= magic.f - if v.f >= inf_or_nan.f { - v.u |= 255 << 23 - } - v.u |= u32(value & 0x8000) << 16 - return v.f -} - - -@(link_name="__gnu_f2h_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -gnu_f2h_ieee :: proc "c" (value: f32) -> __float16 { - return truncsfhf2(value) -} - -@(link_name="__extendhfsf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -extendhfsf2 :: proc "c" (value: __float16) -> f32 { - return gnu_h2f_ieee(value) -} - - - -@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -floattidf :: proc "c" (a: i128) -> f64 { -when IS_WASM { - return 0 -} else { - DBL_MANT_DIG :: 53 - if a == 0 { - return 0.0 - } - a := a - N :: size_of(i128) * 8 - s := a >> (N-1) - a = (a ~ s) - s - sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits - e := i32(sd - 1) // exponent - if sd > DBL_MANT_DIG { - switch sd { - case DBL_MANT_DIG + 1: - a <<= 1 - case DBL_MANT_DIG + 2: - // okay - case: - a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | - i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) - } - - a |= i128((a & 4) != 0) - a += 1 - a >>= 2 - - if a & (i128(1) << DBL_MANT_DIG) != 0 { - a >>= 1 - e += 1 - } - } else { - a <<= u128(DBL_MANT_DIG - sd) & 127 - } - fb: [2]u32 - fb[1] = (u32(s) & 0x80000000) | // sign - (u32(e + 1023) << 20) | // exponent - u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high - fb[0] = u32(a) // mantissa-low - return transmute(f64)fb -} -} - - -@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -floattidf_unsigned :: proc "c" (a: u128) -> f64 { -when IS_WASM { - return 0 -} else { - DBL_MANT_DIG :: 53 - if a == 0 { - return 0.0 - } - a := a - N :: size_of(u128) * 8 - sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits - e := i32(sd - 1) // exponent - if sd > DBL_MANT_DIG { - switch sd { - case DBL_MANT_DIG + 1: - a <<= 1 - case DBL_MANT_DIG + 2: - // okay - case: - a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | - u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) - } - - a |= u128((a & 4) != 0) - a += 1 - a >>= 2 - - if a & (1 << DBL_MANT_DIG) != 0 { - a >>= 1 - e += 1 - } - } else { - a <<= u128(DBL_MANT_DIG - sd) - } - fb: [2]u32 - fb[1] = (0) | // sign - u32((e + 1023) << 20) | // exponent - u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high - fb[0] = u32(a) // mantissa-low - return transmute(f64)fb -} -} - - - -@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 { - // TODO(bill): implement `fixunsdfti` correctly - x := u64(a) - return u128(x) -} - -@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 { - // TODO(bill): implement `fixunsdfdi` correctly - x := i64(a) - return i128(x) -} - - - - -@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -umodti3 :: proc "c" (a, b: u128) -> u128 { - r: u128 = --- - _ = udivmod128(a, b, &r) - return r -} - - -@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { - return udivmod128(a, b, rem) -} - -@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -udivti3 :: proc "c" (a, b: u128) -> u128 { - return udivmodti4(a, b, nil) -} - - -@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -modti3 :: proc "c" (a, b: i128) -> i128 { - s_a := a >> (128 - 1) - s_b := b >> (128 - 1) - an := (a ~ s_a) - s_a - bn := (b ~ s_b) - s_b - - r: u128 = --- - _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) - return (transmute(i128)r ~ s_a) - s_a -} - - -@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { - u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) - return transmute(i128)u -} - -@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) - return transmute(i128)u -} - - -@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixdfti :: proc(a: u64) -> i128 { - significandBits :: 52 - typeWidth :: (size_of(u64)*8) - exponentBits :: (typeWidth - significandBits - 1) - maxExponent :: ((1 << exponentBits) - 1) - exponentBias :: (maxExponent >> 1) - - implicitBit :: (u64(1) << significandBits) - significandMask :: (implicitBit - 1) - signBit :: (u64(1) << (significandBits + exponentBits)) - absMask :: (signBit - 1) - exponentMask :: (absMask ~ significandMask) - - // Break a into sign, exponent, significand - aRep := a - aAbs := aRep & absMask - sign := i128(-1 if aRep & signBit != 0 else 1) - exponent := u64((aAbs >> significandBits) - exponentBias) - significand := u64((aAbs & significandMask) | implicitBit) - - // If exponent is negative, the result is zero. - if exponent < 0 { - return 0 - } - - // If the value is too large for the integer type, saturate. - if exponent >= size_of(i128) * 8 { - return max(i128) if sign == 1 else min(i128) - } - - // If 0 <= exponent < significandBits, right shift to get the result. - // Otherwise, shift left. - if exponent < significandBits { - return sign * i128(significand >> (significandBits - exponent)) - } else { - return sign * (i128(significand) << (exponent - significandBits)) - } - -} diff --git a/core/runtime/os_specific.odin b/core/runtime/os_specific.odin deleted file mode 100644 index 022d315d4..000000000 --- a/core/runtime/os_specific.odin +++ /dev/null @@ -1,7 +0,0 @@ -package runtime - -_OS_Errno :: distinct int - -os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - return _os_write(data) -} diff --git a/core/runtime/os_specific_any.odin b/core/runtime/os_specific_any.odin deleted file mode 100644 index 6a96655c4..000000000 --- a/core/runtime/os_specific_any.odin +++ /dev/null @@ -1,16 +0,0 @@ -//+build !darwin -//+build !freestanding -//+build !js -//+build !wasi -//+build !windows -package runtime - -import "core:os" - -// TODO(bill): reimplement `os.write` so that it does not rely on package os -// NOTE: Use os_specific_linux.odin, os_specific_darwin.odin, etc -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - context = default_context() - n, err := os.write(os.stderr, data) - return int(n), _OS_Errno(err) -} diff --git a/core/runtime/os_specific_darwin.odin b/core/runtime/os_specific_darwin.odin deleted file mode 100644 index 5de9a7d57..000000000 --- a/core/runtime/os_specific_darwin.odin +++ /dev/null @@ -1,12 +0,0 @@ -//+build darwin -package runtime - -import "core:intrinsics" - -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - ret := intrinsics.syscall(0x2000004, 1, uintptr(raw_data(data)), uintptr(len(data))) - if ret < 0 { - return 0, _OS_Errno(-ret) - } - return int(ret), 0 -} diff --git a/core/runtime/os_specific_freestanding.odin b/core/runtime/os_specific_freestanding.odin deleted file mode 100644 index a6d04cefb..000000000 --- a/core/runtime/os_specific_freestanding.odin +++ /dev/null @@ -1,7 +0,0 @@ -//+build freestanding -package runtime - -// TODO(bill): reimplement `os.write` -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - return 0, -1 -} diff --git a/core/runtime/os_specific_js.odin b/core/runtime/os_specific_js.odin deleted file mode 100644 index 246141d87..000000000 --- a/core/runtime/os_specific_js.odin +++ /dev/null @@ -1,12 +0,0 @@ -//+build js -package runtime - -foreign import "odin_env" - -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - foreign odin_env { - write :: proc "contextless" (fd: u32, p: []byte) --- - } - write(1, data) - return len(data), 0 -} diff --git a/core/runtime/os_specific_wasi.odin b/core/runtime/os_specific_wasi.odin deleted file mode 100644 index 3f69504ee..000000000 --- a/core/runtime/os_specific_wasi.odin +++ /dev/null @@ -1,10 +0,0 @@ -//+build wasi -package runtime - -import "core:sys/wasm/wasi" - -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - data := (wasi.ciovec_t)(data) - n, err := wasi.fd_write(1, {data}) - return int(n), _OS_Errno(err) -} diff --git a/core/runtime/os_specific_windows.odin b/core/runtime/os_specific_windows.odin deleted file mode 100644 index 4a5907466..000000000 --- a/core/runtime/os_specific_windows.odin +++ /dev/null @@ -1,135 +0,0 @@ -//+build windows -package runtime - -foreign import kernel32 "system:Kernel32.lib" - -@(private="file") -@(default_calling_convention="system") -foreign kernel32 { - // NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency - - // os_write - GetStdHandle :: proc(which: u32) -> rawptr --- - SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 --- - WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 --- - GetLastError :: proc() -> u32 --- - - // default_allocator - GetProcessHeap :: proc() -> rawptr --- - HeapAlloc :: proc(hHeap: rawptr, dwFlags: u32, dwBytes: uint) -> rawptr --- - HeapReAlloc :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr, dwBytes: uint) -> rawptr --- - HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 --- -} - -_os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check { - if len(data) == 0 { - return 0, 0 - } - - STD_ERROR_HANDLE :: ~u32(0) -12 + 1 - HANDLE_FLAG_INHERIT :: 0x00000001 - MAX_RW :: 1<<30 - - h := GetStdHandle(STD_ERROR_HANDLE) - when size_of(uintptr) == 8 { - SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0) - } - - single_write_length: u32 - total_write: i64 - length := i64(len(data)) - - for total_write < length { - remaining := length - total_write - to_write := u32(min(i32(remaining), MAX_RW)) - - e := WriteFile(h, &data[total_write], to_write, &single_write_length, nil) - if single_write_length <= 0 || !e { - err = _OS_Errno(GetLastError()) - n = int(total_write) - return - } - total_write += i64(single_write_length) - } - n = int(total_write) - return -} - -heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { - HEAP_ZERO_MEMORY :: 0x00000008 - return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) -} -heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { - if new_size == 0 { - heap_free(ptr) - return nil - } - if ptr == nil { - return heap_alloc(new_size) - } - - HEAP_ZERO_MEMORY :: 0x00000008 - return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) -} -heap_free :: proc "contextless" (ptr: rawptr) { - if ptr == nil { - return - } - HeapFree(GetProcessHeap(), 0, ptr) -} - - -// -// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. -// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert -// padding. We also store the original pointer returned by heap_alloc right before -// the pointer we return to the user. -// - - - -_windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, Allocator_Error) { - if size == 0 { - _windows_default_free(old_ptr) - return nil, nil - } - - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := ([^]rawptr)(old_ptr)[-1] - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) - } - aligned_mem := ([^]u8)(allocated_mem)[size_of(rawptr):] - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space || allocated_mem == nil { - return nil, .Out_Of_Memory - } - - aligned_mem = ([^]byte)(aligned_ptr) - ([^]rawptr)(aligned_mem)[-1] = allocated_mem - - return aligned_mem[:size], nil -} - -_windows_default_alloc :: proc "contextless" (size, alignment: int, zero_memory := true) -> ([]byte, Allocator_Error) { - return _windows_default_alloc_or_resize(size, alignment, nil, zero_memory) -} - - -_windows_default_free :: proc "contextless" (ptr: rawptr) { - if ptr != nil { - heap_free(([^]rawptr)(ptr)[-1]) - } -} - -_windows_default_resize :: proc "contextless" (p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, Allocator_Error) { - return _windows_default_alloc_or_resize(new_size, new_alignment, p) -} diff --git a/core/runtime/print.odin b/core/runtime/print.odin deleted file mode 100644 index 87c8757d5..000000000 --- a/core/runtime/print.odin +++ /dev/null @@ -1,489 +0,0 @@ -package runtime - -_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz" - -@(private="file") -_INTEGER_DIGITS_VAR := _INTEGER_DIGITS - -when !ODIN_NO_RTTI { - print_any_single :: proc "contextless" (arg: any) { - x := arg - if x.data == nil { - print_string("nil") - return - } - - if loc, ok := x.(Source_Code_Location); ok { - print_caller_location(loc) - return - } - x.id = typeid_base(x.id) - switch v in x { - case typeid: print_typeid(v) - case ^Type_Info: print_type(v) - - case string: print_string(v) - case cstring: print_string(string(v)) - case []byte: print_string(string(v)) - - case rune: print_rune(v) - - case u8: print_u64(u64(v)) - case u16: print_u64(u64(v)) - case u16le: print_u64(u64(v)) - case u16be: print_u64(u64(v)) - case u32: print_u64(u64(v)) - case u32le: print_u64(u64(v)) - case u32be: print_u64(u64(v)) - case u64: print_u64(u64(v)) - case u64le: print_u64(u64(v)) - case u64be: print_u64(u64(v)) - - case i8: print_i64(i64(v)) - case i16: print_i64(i64(v)) - case i16le: print_i64(i64(v)) - case i16be: print_i64(i64(v)) - case i32: print_i64(i64(v)) - case i32le: print_i64(i64(v)) - case i32be: print_i64(i64(v)) - case i64: print_i64(i64(v)) - case i64le: print_i64(i64(v)) - case i64be: print_i64(i64(v)) - - case int: print_int(v) - case uint: print_uint(v) - case uintptr: print_uintptr(v) - case rawptr: print_uintptr(uintptr(v)) - - case bool: print_string("true" if v else "false") - case b8: print_string("true" if v else "false") - case b16: print_string("true" if v else "false") - case b32: print_string("true" if v else "false") - case b64: print_string("true" if v else "false") - - case: - ti := type_info_of(x.id) - #partial switch v in ti.variant { - case Type_Info_Pointer, Type_Info_Multi_Pointer: - print_uintptr((^uintptr)(x.data)^) - return - } - - print_string("") - } - } - println_any :: proc "contextless" (args: ..any) { - context = default_context() - loop: for arg, i in args { - assert(arg.id != nil) - if i != 0 { - print_string(" ") - } - print_any_single(arg) - } - print_string("\n") - } -} - - -encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) { - r := c - - buf: [4]u8 - i := u32(r) - mask :: u8(0x3f) - if i <= 1<<7-1 { - buf[0] = u8(r) - return buf, 1 - } - if i <= 1<<11-1 { - buf[0] = 0xc0 | u8(r>>6) - buf[1] = 0x80 | u8(r) & mask - return buf, 2 - } - - // Invalid or Surrogate range - if i > 0x0010ffff || - (0xd800 <= i && i <= 0xdfff) { - r = 0xfffd - } - - if i <= 1<<16-1 { - buf[0] = 0xe0 | u8(r>>12) - buf[1] = 0x80 | u8(r>>6) & mask - buf[2] = 0x80 | u8(r) & mask - return buf, 3 - } - - buf[0] = 0xf0 | u8(r>>18) - buf[1] = 0x80 | u8(r>>12) & mask - buf[2] = 0x80 | u8(r>>6) & mask - buf[3] = 0x80 | u8(r) & mask - return buf, 4 -} - -print_string :: proc "contextless" (str: string) -> (n: int) { - n, _ = os_write(transmute([]byte)str) - return -} - -print_strings :: proc "contextless" (args: ..string) -> (n: int) { - for str in args { - m, err := os_write(transmute([]byte)str) - n += m - if err != 0 { - break - } - } - return -} - -print_byte :: proc "contextless" (b: byte) -> (n: int) { - n, _ = os_write([]byte{b}) - return -} - -print_encoded_rune :: proc "contextless" (r: rune) { - print_byte('\'') - - switch r { - case '\a': print_string("\\a") - case '\b': print_string("\\b") - case '\e': print_string("\\e") - case '\f': print_string("\\f") - case '\n': print_string("\\n") - case '\r': print_string("\\r") - case '\t': print_string("\\t") - case '\v': print_string("\\v") - case: - if r <= 0 { - print_string("\\x00") - } else if r < 32 { - n0, n1 := u8(r) >> 4, u8(r) & 0xf - print_string("\\x") - print_byte(_INTEGER_DIGITS_VAR[n0]) - print_byte(_INTEGER_DIGITS_VAR[n1]) - } else { - print_rune(r) - } - } - print_byte('\'') -} - -print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { - RUNE_SELF :: 0x80 - - if r < RUNE_SELF { - return print_byte(byte(r)) - } - - b, n := encode_rune(r) - m, _ := os_write(b[:n]) - return m -} - - -print_u64 :: proc "contextless" (x: u64) #no_bounds_check { - a: [129]byte - i := len(a) - b := u64(10) - u := x - for u >= b { - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - u /= b - } - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - - os_write(a[i:]) -} - - -print_i64 :: proc "contextless" (x: i64) #no_bounds_check { - b :: i64(10) - - u := x - neg := u < 0 - u = abs(u) - - a: [129]byte - i := len(a) - for u >= b { - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - u /= b - } - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - if neg { - i -= 1; a[i] = '-' - } - - os_write(a[i:]) -} - -print_uint :: proc "contextless" (x: uint) { print_u64(u64(x)) } -print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) } -print_int :: proc "contextless" (x: int) { print_i64(i64(x)) } - -print_caller_location :: proc "contextless" (loc: Source_Code_Location) { - print_string(loc.file_path) - when ODIN_ERROR_POS_STYLE == .Default { - print_byte('(') - print_u64(u64(loc.line)) - print_byte(':') - print_u64(u64(loc.column)) - print_byte(')') - } else when ODIN_ERROR_POS_STYLE == .Unix { - print_byte(':') - print_u64(u64(loc.line)) - print_byte(':') - print_u64(u64(loc.column)) - print_byte(':') - } else { - #panic("unhandled ODIN_ERROR_POS_STYLE") - } -} -print_typeid :: proc "contextless" (id: typeid) { - when ODIN_NO_RTTI { - if id == nil { - print_string("nil") - } else { - print_string("") - } - } else { - if id == nil { - print_string("nil") - } else { - ti := type_info_of(id) - print_type(ti) - } - } -} -print_type :: proc "contextless" (ti: ^Type_Info) { - if ti == nil { - print_string("nil") - return - } - - switch info in ti.variant { - case Type_Info_Named: - print_string(info.name) - case Type_Info_Integer: - switch ti.id { - case int: print_string("int") - case uint: print_string("uint") - case uintptr: print_string("uintptr") - case: - print_byte('i' if info.signed else 'u') - print_u64(u64(8*ti.size)) - } - case Type_Info_Rune: - print_string("rune") - case Type_Info_Float: - print_byte('f') - print_u64(u64(8*ti.size)) - case Type_Info_Complex: - print_string("complex") - print_u64(u64(8*ti.size)) - case Type_Info_Quaternion: - print_string("quaternion") - print_u64(u64(8*ti.size)) - case Type_Info_String: - print_string("string") - case Type_Info_Boolean: - switch ti.id { - case bool: print_string("bool") - case: - print_byte('b') - print_u64(u64(8*ti.size)) - } - case Type_Info_Any: - print_string("any") - case Type_Info_Type_Id: - print_string("typeid") - - case Type_Info_Pointer: - if info.elem == nil { - print_string("rawptr") - } else { - print_string("^") - print_type(info.elem) - } - case Type_Info_Multi_Pointer: - print_string("[^]") - print_type(info.elem) - case Type_Info_Soa_Pointer: - print_string("#soa ^") - print_type(info.elem) - case Type_Info_Procedure: - print_string("proc") - if info.params == nil { - print_string("()") - } else { - t := info.params.variant.(Type_Info_Parameters) - print_byte('(') - for t, i in t.types { - if i > 0 { print_string(", ") } - print_type(t) - } - print_string(")") - } - if info.results != nil { - print_string(" -> ") - print_type(info.results) - } - case Type_Info_Parameters: - count := len(info.names) - if count != 1 { print_byte('(') } - for name, i in info.names { - if i > 0 { print_string(", ") } - - t := info.types[i] - - if len(name) > 0 { - print_string(name) - print_string(": ") - } - print_type(t) - } - if count != 1 { print_string(")") } - - case Type_Info_Array: - print_byte('[') - print_u64(u64(info.count)) - print_byte(']') - print_type(info.elem) - - case Type_Info_Enumerated_Array: - if info.is_sparse { - print_string("#sparse") - } - print_byte('[') - print_type(info.index) - print_byte(']') - print_type(info.elem) - - - case Type_Info_Dynamic_Array: - print_string("[dynamic]") - print_type(info.elem) - case Type_Info_Slice: - print_string("[]") - print_type(info.elem) - - case Type_Info_Map: - print_string("map[") - print_type(info.key) - print_byte(']') - print_type(info.value) - - case Type_Info_Struct: - switch info.soa_kind { - case .None: // Ignore - case .Fixed: - print_string("#soa[") - print_u64(u64(info.soa_len)) - print_byte(']') - print_type(info.soa_base_type) - return - case .Slice: - print_string("#soa[]") - print_type(info.soa_base_type) - return - case .Dynamic: - print_string("#soa[dynamic]") - print_type(info.soa_base_type) - return - } - - print_string("struct ") - if info.is_packed { print_string("#packed ") } - if info.is_raw_union { print_string("#raw_union ") } - if info.custom_align { - print_string("#align(") - print_u64(u64(ti.align)) - print_string(") ") - } - print_byte('{') - for name, i in info.names { - if i > 0 { print_string(", ") } - print_string(name) - print_string(": ") - print_type(info.types[i]) - } - print_byte('}') - - case Type_Info_Union: - print_string("union ") - if info.custom_align { - print_string("#align(") - print_u64(u64(ti.align)) - print_string(") ") - } - if info.no_nil { - print_string("#no_nil ") - } - print_byte('{') - for variant, i in info.variants { - if i > 0 { print_string(", ") } - print_type(variant) - } - print_string("}") - - case Type_Info_Enum: - print_string("enum ") - print_type(info.base) - print_string(" {") - for name, i in info.names { - if i > 0 { print_string(", ") } - print_string(name) - } - print_string("}") - - case Type_Info_Bit_Set: - print_string("bit_set[") - - #partial switch elem in type_info_base(info.elem).variant { - case Type_Info_Enum: - print_type(info.elem) - case Type_Info_Rune: - print_encoded_rune(rune(info.lower)) - print_string("..") - print_encoded_rune(rune(info.upper)) - case: - print_i64(info.lower) - print_string("..") - print_i64(info.upper) - } - if info.underlying != nil { - print_string("; ") - print_type(info.underlying) - } - print_byte(']') - - - case Type_Info_Simd_Vector: - print_string("#simd[") - print_u64(u64(info.count)) - print_byte(']') - print_type(info.elem) - - case Type_Info_Relative_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) - - case Type_Info_Relative_Multi_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) - - case Type_Info_Matrix: - print_string("matrix[") - print_u64(u64(info.row_count)) - print_string(", ") - print_u64(u64(info.column_count)) - print_string("]") - print_type(info.elem) - } -} diff --git a/core/runtime/procs.odin b/core/runtime/procs.odin deleted file mode 100644 index 454574c35..000000000 --- a/core/runtime/procs.odin +++ /dev/null @@ -1,95 +0,0 @@ -package runtime - -when ODIN_NO_CRT && ODIN_OS == .Windows { - foreign import lib "system:NtDll.lib" - - @(private="file") - @(default_calling_convention="system") - foreign lib { - RtlMoveMemory :: proc(dst, s: rawptr, length: int) --- - RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- - } - - @(link_name="memset", linkage="strong", require) - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { - RtlFillMemory(ptr, len, val) - return ptr - } - @(link_name="memmove", linkage="strong", require) - memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - RtlMoveMemory(dst, src, len) - return dst - } - @(link_name="memcpy", linkage="strong", require) - memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - RtlMoveMemory(dst, src, len) - return dst - } -} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { - @(link_name="memset", linkage="strong", require) - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { - if ptr != nil && len != 0 { - b := byte(val) - p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { - p[i] = b - } - } - return ptr - } - - @(link_name="bzero", linkage="strong", require) - bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr { - if ptr != nil && len != 0 { - p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { - p[i] = 0 - } - } - return ptr - } - - @(link_name="memmove", linkage="strong", require) - memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - d, s := ([^]byte)(dst), ([^]byte)(src) - if d == s || len == 0 { - return dst - } - if d > s && uintptr(d)-uintptr(s) < uintptr(len) { - for i := len-1; i >= 0; i -= 1 { - d[i] = s[i] - } - return dst - } - - if s > d && uintptr(s)-uintptr(d) < uintptr(len) { - for i := 0; i < len; i += 1 { - d[i] = s[i] - } - return dst - } - return memcpy(dst, src, len) - } - @(link_name="memcpy", linkage="strong", require) - memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - d, s := ([^]byte)(dst), ([^]byte)(src) - if d != s { - for i := 0; i < len; i += 1 { - d[i] = s[i] - } - } - return d - - } -} else { - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { - if ptr != nil && len != 0 { - b := byte(val) - p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { - p[i] = b - } - } - return ptr - } -} \ No newline at end of file diff --git a/core/runtime/procs_darwin.odin b/core/runtime/procs_darwin.odin deleted file mode 100644 index 9c53b5b16..000000000 --- a/core/runtime/procs_darwin.odin +++ /dev/null @@ -1,21 +0,0 @@ -//+private -package runtime - -foreign import "system:Foundation.framework" - -import "core:intrinsics" - -objc_id :: ^intrinsics.objc_object -objc_Class :: ^intrinsics.objc_class -objc_SEL :: ^intrinsics.objc_selector - -foreign Foundation { - objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- - sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- - objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- - - objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- - objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- - objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- - objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- -} diff --git a/core/runtime/procs_js.odin b/core/runtime/procs_js.odin deleted file mode 100644 index d3e12410c..000000000 --- a/core/runtime/procs_js.odin +++ /dev/null @@ -1,15 +0,0 @@ -//+build js -package runtime - -init_default_context_for_js: Context -@(init, private="file") -init_default_context :: proc() { - init_default_context_for_js = context -} - -@(export) -@(link_name="default_context_ptr") -default_context_ptr :: proc "contextless" () -> ^Context { - return &init_default_context_for_js -} - diff --git a/core/runtime/procs_wasm.odin b/core/runtime/procs_wasm.odin deleted file mode 100644 index 26dcfef77..000000000 --- a/core/runtime/procs_wasm.odin +++ /dev/null @@ -1,40 +0,0 @@ -//+build wasm32, wasm64p32 -package runtime - -@(private="file") -ti_int :: struct #raw_union { - using s: struct { lo, hi: u64 }, - all: i128, -} - -@(link_name="__ashlti3", linkage="strong") -__ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 { - bits_in_dword :: size_of(u32)*8 - b := u32(b_) - - input, result: ti_int - input.all = a - if b & bits_in_dword != 0 { - result.lo = 0 - result.hi = input.lo << (b-bits_in_dword) - } else { - if b == 0 { - return a - } - result.lo = input.lo<>(bits_in_dword-b)) - } - return result.all -} - - -@(link_name="__multi3", linkage="strong") -__multi3 :: proc "contextless" (a, b: i128) -> i128 { - x, y, r: ti_int - - x.all = a - y.all = b - r.all = i128(x.lo * y.lo) // TODO this is incorrect - r.hi += x.hi*y.lo + x.lo*y.hi - return r.all -} \ No newline at end of file diff --git a/core/runtime/procs_windows_amd64.asm b/core/runtime/procs_windows_amd64.asm deleted file mode 100644 index f588b3453..000000000 --- a/core/runtime/procs_windows_amd64.asm +++ /dev/null @@ -1,79 +0,0 @@ -bits 64 - -global __chkstk -global _tls_index -global _fltused - -section .data - _tls_index: dd 0 - _fltused: dd 0x9875 - -section .text -; NOTE(flysand): The function call to __chkstk is called -; by the compiler, when we're allocating arrays larger than -; a page size. The reason is because the OS doesn't map the -; whole stack into memory all at once, but does so page-by-page. -; When the next page is touched, the CPU generates a page fault, -; which *the OS* is handling by allocating the next page in the -; stack until we reach the limit of stack size. -; -; This page is called the guard page, touching it will extend -; the size of the stack and overwrite the stack limit in the TEB. -; -; If we allocate a large enough array and start writing from the -; bottom of it, it's possible that we may start touching -; non-contiguous pages which are unmapped. OS only maps the stack -; page into the memory if the page above it was also mapped. -; -; Therefore the compilers insert this routine, the sole purpose -; of which is to step through the stack starting from the RSP -; down to the new RSP after allocation, and touch every page -; of the new allocation so that the stack is fully mapped for -; the new allocation -; -; I've gotten this code by disassembling the output of MSVC long -; time ago. I don't remember if I've cleaned it up, but it definately -; stinks. -; -; Additional notes: -; RAX (passed as parameter) holds the allocation's size -; GS:[0x10] references the current stack limit -; (i.e. bottom of the stack (i.e. lowest address accessible)) -; -; Also this stuff is windows-only kind of thing, because linux people -; didn't think stack that grows is cool enough for them, but the kernel -; totally supports this kind of stack. -__chkstk: - ;; Allocate 16 bytes to store values of r10 and r11 - sub rsp, 0x10 - mov [rsp], r10 - mov [rsp+0x8], r11 - ;; Set r10 to point to the stack as of the moment of the function call - lea r10, [rsp+0x18] - ;; Subtract r10 til the bottom of the stack allocation, if we overflow - ;; reset r10 to 0, we'll crash with segfault anyway - xor r11, r11 - sub r10, rax - cmovb r10, r11 - ;; Load r11 with the bottom of the stack (lowest allocated address) - mov r11, gs:[0x10] ; NOTE(flysand): gs:[0x10] is stack limit - ;; If the bottom of the allocation is above the bottom of the stack, - ;; we don't need to probe - cmp r10, r11 - jnb .end - ;; Align the bottom of the allocation down to page size - and r10w, 0xf000 -.loop: - ;; Move the pointer to the next guard page, and touch it by loading 0 - ;; into that page - lea r11, [r11-0x1000] - mov byte [r11], 0x0 - ;; Did we reach the bottom of the allocation? - cmp r10, r11 - jnz .loop -.end: - ;; Restore previous r10 and r11 and return - mov r10, [rsp] - mov r11, [rsp+0x8] - add rsp, 0x10 - ret \ No newline at end of file diff --git a/core/runtime/procs_windows_amd64.odin b/core/runtime/procs_windows_amd64.odin deleted file mode 100644 index ea495f5fa..000000000 --- a/core/runtime/procs_windows_amd64.odin +++ /dev/null @@ -1,26 +0,0 @@ -//+private -//+no-instrumentation -package runtime - -foreign import kernel32 "system:Kernel32.lib" - -@(private) -foreign kernel32 { - RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! --- -} - -windows_trap_array_bounds :: proc "contextless" () -> ! { - EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C - - - RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) -} - -windows_trap_type_assertion :: proc "contextless" () -> ! { - windows_trap_array_bounds() -} - -when ODIN_NO_CRT { - @(require) - foreign import crt_lib "procs_windows_amd64.asm" -} diff --git a/core/runtime/procs_windows_i386.odin b/core/runtime/procs_windows_i386.odin deleted file mode 100644 index 10422cf07..000000000 --- a/core/runtime/procs_windows_i386.odin +++ /dev/null @@ -1,29 +0,0 @@ -//+private -//+no-instrumentation -package runtime - -@require foreign import "system:int64.lib" - -foreign import kernel32 "system:Kernel32.lib" - -windows_trap_array_bounds :: proc "contextless" () -> ! { - DWORD :: u32 - ULONG_PTR :: uint - - EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C - - foreign kernel32 { - RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- - } - - RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) -} - -windows_trap_type_assertion :: proc "contextless" () -> ! { - windows_trap_array_bounds() -} - -@(private, export, link_name="_fltused") _fltused: i32 = 0x9875 - -@(private, export, link_name="_tls_index") _tls_index: u32 -@(private, export, link_name="_tls_array") _tls_array: u32 diff --git a/core/runtime/udivmod128.odin b/core/runtime/udivmod128.odin deleted file mode 100644 index 87ef73c2c..000000000 --- a/core/runtime/udivmod128.odin +++ /dev/null @@ -1,156 +0,0 @@ -package runtime - -import "core:intrinsics" - -udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { - _ctz :: intrinsics.count_trailing_zeros - _clz :: intrinsics.count_leading_zeros - - n := transmute([2]u64)a - d := transmute([2]u64)b - q, r: [2]u64 - sr: u32 = 0 - - low :: 1 when ODIN_ENDIAN == .Big else 0 - high :: 1 - low - U64_BITS :: 8*size_of(u64) - U128_BITS :: 8*size_of(u128) - - // Special Cases - - if n[high] == 0 { - if d[high] == 0 { - if rem != nil { - res := n[low] % d[low] - rem^ = u128(res) - } - return u128(n[low] / d[low]) - } - - if rem != nil { - rem^ = u128(n[low]) - } - return 0 - } - - if d[low] == 0 { - if d[high] == 0 { - if rem != nil { - rem^ = u128(n[high] % d[low]) - } - return u128(n[high] / d[low]) - } - if n[low] == 0 { - if rem != nil { - r[high] = n[high] % d[high] - r[low] = 0 - rem^ = transmute(u128)r - } - return u128(n[high] / d[high]) - } - - if d[high] & (d[high]-1) == 0 { - if rem != nil { - r[low] = n[low] - r[high] = n[high] & (d[high] - 1) - rem^ = transmute(u128)r - } - return u128(n[high] >> _ctz(d[high])) - } - - sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) - if sr > U64_BITS - 2 { - if rem != nil { - rem^ = a - } - return 0 - } - - sr += 1 - - q[low] = 0 - q[high] = n[low] << u64(U64_BITS - sr) - r[high] = n[high] >> sr - r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) - } else { - if d[high] == 0 { - if d[low] & (d[low] - 1) == 0 { - if rem != nil { - rem^ = u128(n[low] & (d[low] - 1)) - } - if d[low] == 1 { - return a - } - sr = u32(_ctz(d[low])) - q[high] = n[high] >> sr - q[low] = (n[high] << (U64_BITS-sr)) | (n[low] >> sr) - return transmute(u128)q - } - - sr = 1 + U64_BITS + u32(_clz(d[low])) - u32(_clz(n[high])) - - switch { - case sr == U64_BITS: - q[low] = 0 - q[high] = n[low] - r[high] = 0 - r[low] = n[high] - case sr < U64_BITS: - q[low] = 0 - q[high] = n[low] << (U64_BITS - sr) - r[high] = n[high] >> sr - r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) - case: - q[low] = n[low] << (U128_BITS - sr) - q[high] = (n[high] << (U128_BITS - sr)) | (n[low] >> (sr - U64_BITS)) - r[high] = 0 - r[low] = n[high] >> (sr - U64_BITS) - } - } else { - sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) - - if sr > U64_BITS - 1 { - if rem != nil { - rem^ = a - } - return 0 - } - - sr += 1 - - q[low] = 0 - if sr == U64_BITS { - q[high] = n[low] - r[high] = 0 - r[low] = n[high] - } else { - r[high] = n[high] >> sr - r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) - q[high] = n[low] << (U64_BITS - sr) - } - } - } - - carry: u32 = 0 - r_all: u128 - - for ; sr > 0; sr -= 1 { - r[high] = (r[high] << 1) | (r[low] >> (U64_BITS - 1)) - r[low] = (r[low] << 1) | (q[high] >> (U64_BITS - 1)) - q[high] = (q[high] << 1) | (q[low] >> (U64_BITS - 1)) - q[low] = (q[low] << 1) | u64(carry) - - r_all = transmute(u128)r - s := i128(b - r_all - 1) >> (U128_BITS - 1) - carry = u32(s & 1) - r_all -= b & transmute(u128)s - r = transmute([2]u64)r_all - } - - q_all := ((transmute(u128)q) << 1) | u128(carry) - if rem != nil { - rem^ = r_all - } - - return q_all -} diff --git a/src/build_settings.cpp b/src/build_settings.cpp index af518bcb4..8c9e13178 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1161,7 +1161,27 @@ gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String } -gb_internal String get_fullpath_core(gbAllocator a, String path) { +gb_internal String get_fullpath_base_collection(gbAllocator a, String path) { + String module_dir = odin_root_dir(); + + String base = str_lit("base/"); + + isize str_len = module_dir.len + base.len + path.len; + u8 *str = gb_alloc_array(heap_allocator(), u8, str_len+1); + defer (gb_free(heap_allocator(), str)); + + isize i = 0; + gb_memmove(str+i, module_dir.text, module_dir.len); i += module_dir.len; + gb_memmove(str+i, base.text, base.len); i += base.len; + gb_memmove(str+i, path.text, path.len); i += path.len; + str[i] = 0; + + String res = make_string(str, i); + res = string_trim_whitespace(res); + return path_to_fullpath(a, res); +} + +gb_internal String get_fullpath_core_collection(gbAllocator a, String path) { String module_dir = odin_root_dir(); String core = str_lit("core/"); diff --git a/src/checker.cpp b/src/checker.cpp index 498fce7d2..563bb2781 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -770,15 +770,17 @@ gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type * rw_mutex_unlock(&d->type_info_deps_mutex); } -gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { + +gb_internal AstPackage *get_runtime_package(CheckerInfo *info) { + String name = str_lit("runtime"); gbAllocator a = heap_allocator(); - String path = get_fullpath_core(a, name); + String path = get_fullpath_base_collection(a, name); defer (gb_free(a, path.text)); auto found = string_map_get(&info->packages, path); if (found == nullptr) { gb_printf_err("Name: %.*s\n", LIT(name)); gb_printf_err("Fullpath: %.*s\n", LIT(path)); - + for (auto const &entry : info->packages) { gb_printf_err("%.*s\n", LIT(entry.key)); } @@ -787,6 +789,26 @@ gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { return *found; } +gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { + if (name == "runtime") { + return get_runtime_package(info); + } + + gbAllocator a = heap_allocator(); + String path = get_fullpath_core_collection(a, name); + defer (gb_free(a, path.text)); + auto found = string_map_get(&info->packages, path); + if (found == nullptr) { + gb_printf_err("Name: %.*s\n", LIT(name)); + gb_printf_err("Fullpath: %.*s\n", LIT(path)); + + for (auto const &entry : info->packages) { + gb_printf_err("%.*s\n", LIT(entry.key)); + } + GB_ASSERT_MSG(found != nullptr, "Missing core package %.*s", LIT(name)); + } + return *found; +} gb_internal void add_package_dependency(CheckerContext *c, char const *package_name, char const *name) { String n = make_string_c(name); diff --git a/src/main.cpp b/src/main.cpp index 19271d667..5cff99160 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2376,6 +2376,7 @@ int main(int arg_count, char const **arg_ptr) { TIME_SECTION("init default library collections"); array_init(&library_collections, heap_allocator()); // NOTE(bill): 'core' cannot be (re)defined by the user + add_library_collection(str_lit("base"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("base"))); add_library_collection(str_lit("core"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("core"))); add_library_collection(str_lit("vendor"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("vendor"))); diff --git a/src/parser.cpp b/src/parser.cpp index b16a88de5..9ed3e32f9 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5460,6 +5460,11 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (collection_name.len > 0) { + // NOTE(bill): `base:runtime` == `core:runtime` + if (collection_name == "core" && string_starts_with(file_str, str_lit("runtime"))) { + collection_name = str_lit("base"); + } + if (collection_name == "system") { if (node->kind != Ast_ForeignImportDecl) { syntax_error(node, "The library collection 'system' is restrict for 'foreign_library'"); @@ -5489,7 +5494,6 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node #endif } - if (is_package_name_reserved(file_str)) { *path = file_str; if (collection_name == "core") { @@ -6133,7 +6137,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { { // Add these packages serially and then process them parallel TokenPos init_pos = {}; { - String s = get_fullpath_core(permanent_allocator(), str_lit("runtime")); + String s = get_fullpath_base_collection(permanent_allocator(), str_lit("runtime")); try_add_import_path(p, s, s, init_pos, Package_Runtime); } @@ -6141,7 +6145,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { p->init_fullpath = init_fullpath; if (build_context.command_kind == Command_test) { - String s = get_fullpath_core(permanent_allocator(), str_lit("testing")); + String s = get_fullpath_core_collection(permanent_allocator(), str_lit("testing")); try_add_import_path(p, s, s, init_pos, Package_Normal); } -- cgit v1.2.3 From 395e0fb225816ff9699e82f6d9d5887ef3b1358a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Jan 2024 22:09:20 +0000 Subject: `-default-to-panic-allocator` --- base/runtime/default_allocators_general.odin | 5 +++++ src/build_settings.cpp | 4 +++- src/checker.cpp | 27 ++++++++++++++------------- src/main.cpp | 14 ++++++++++++++ src/parser.cpp | 4 ++-- 5 files changed, 38 insertions(+), 16 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/base/runtime/default_allocators_general.odin b/base/runtime/default_allocators_general.odin index 994a672b0..e3b06af7b 100644 --- a/base/runtime/default_allocators_general.odin +++ b/base/runtime/default_allocators_general.odin @@ -13,6 +13,11 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR { // mem.nil_allocator reimplementation default_allocator_proc :: nil_allocator_proc default_allocator :: nil_allocator +} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR { + _ :: os + + default_allocator_proc :: panic_allocator_proc + default_allocator :: panic_allocator } else { default_allocator_proc :: os.heap_allocator_proc diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 8c9e13178..8204d735f 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -323,6 +323,7 @@ struct BuildContext { bool ODIN_DEBUG; // Odin in debug mode bool ODIN_DISABLE_ASSERT; // Whether the default 'assert' et al is disabled in code or not bool ODIN_DEFAULT_TO_NIL_ALLOCATOR; // Whether the default allocator is a "nil" allocator or not (i.e. it does nothing) + bool ODIN_DEFAULT_TO_PANIC_ALLOCATOR; // Whether the default allocator is a "panic" allocator or not (i.e. panics on any call to it) bool ODIN_FOREIGN_ERROR_PROCEDURES; bool ODIN_VALGRIND_SUPPORT; @@ -1609,7 +1610,8 @@ gb_internal bool init_build_paths(String init_filename) { } - if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR || + build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { bc->no_dynamic_literals = true; } diff --git a/src/checker.cpp b/src/checker.cpp index 47fcd3d8f..565e948f8 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1091,19 +1091,20 @@ gb_internal void init_universal(void) { } - add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); - add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); - add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR); - add_global_bool_constant("ODIN_NO_DYNAMIC_LITERALS", bc->no_dynamic_literals); - add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); - add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); - add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); - add_global_bool_constant("ODIN_NO_ENTRY_POINT", bc->no_entry_point); - add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES); - add_global_bool_constant("ODIN_NO_RTTI", bc->no_rtti); - - add_global_bool_constant("ODIN_VALGRIND_SUPPORT", bc->ODIN_VALGRIND_SUPPORT); - add_global_bool_constant("ODIN_TILDE", bc->tilde_backend); + add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); + add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); + add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR); + add_global_bool_constant("ODIN_DEFAULT_TO_PANIC_ALLOCATOR", bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR); + add_global_bool_constant("ODIN_NO_DYNAMIC_LITERALS", bc->no_dynamic_literals); + add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); + add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); + add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); + add_global_bool_constant("ODIN_NO_ENTRY_POINT", bc->no_entry_point); + add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES); + add_global_bool_constant("ODIN_NO_RTTI", bc->no_rtti); + + add_global_bool_constant("ODIN_VALGRIND_SUPPORT", bc->ODIN_VALGRIND_SUPPORT); + add_global_bool_constant("ODIN_TILDE", bc->tilde_backend); add_global_constant("ODIN_COMPILE_TIMESTAMP", t_untyped_integer, exact_value_i64(odin_compile_timestamp())); diff --git a/src/main.cpp b/src/main.cpp index 5cff99160..d77f135a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -273,6 +273,7 @@ enum BuildFlagKind { BuildFlag_DisallowDo, BuildFlag_DefaultToNilAllocator, + BuildFlag_DefaultToPanicAllocator, BuildFlag_StrictStyle, BuildFlag_ForeignErrorProcedures, BuildFlag_NoRTTI, @@ -460,6 +461,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_DefaultToPanicAllocator, str_lit("default-to-panic-allocator"),BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_StrictStyle, str_lit("strict-style"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ForeignErrorProcedures, str_lit("foreign-error-procedures"), BuildFlagParam_None, Command__does_check); @@ -1122,8 +1124,20 @@ gb_internal bool parse_build_flags(Array args) { break; case BuildFlag_DefaultToNilAllocator: + if (build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + gb_printf_err("'-default-to-panic-allocator' cannot be used with '-default-to-nil-allocator'\n"); + bad_flags = true; + } build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR = true; break; + case BuildFlag_DefaultToPanicAllocator: + if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + gb_printf_err("'-default-to-nil-allocator' cannot be used with '-default-to-panic-allocator'\n"); + bad_flags = true; + } + build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true; + break; + case BuildFlag_ForeignErrorProcedures: build_context.ODIN_FOREIGN_ERROR_PROCEDURES = true; break; diff --git a/src/parser.cpp b/src/parser.cpp index 9ed3e32f9..489d6b5d5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5496,10 +5496,10 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (is_package_name_reserved(file_str)) { *path = file_str; - if (collection_name == "core") { + if (collection_name == "core" || collection_name == "base") { return true; } else { - syntax_error(node, "The package '%.*s' must be imported with the core library collection: 'core:%.*s'", LIT(file_str), LIT(file_str)); + syntax_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); return false; } } -- cgit v1.2.3 From 6a07effdd2cb7c369aa4c9711ff66b840abe3033 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Jan 2024 22:15:28 +0000 Subject: Freestanding - default to nil allocator; wasm - default to panic allocator --- src/build_settings.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 8204d735f..c29a012ec 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1475,6 +1475,16 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta break; } } + + if (bc->metrics.os == TargetOs_freestanding) { + bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR; + } else if (is_arch_wasm()) { + if (bc->metrics.os == TargetOs_js || bc->metrics.os == TargetOs_wasi) { + // TODO(bill): Should these even have a default "heap-like" allocator? + } + bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true; + bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR; + } } #if defined(GB_SYSTEM_WINDOWS) @@ -1609,7 +1619,6 @@ gb_internal bool init_build_paths(String init_filename) { produces_output_file = true; } - if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR || build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { bc->no_dynamic_literals = true; -- cgit v1.2.3 From a08250ac5b88068cf928552e2628d1e3c7ade95c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 7 Feb 2024 17:15:59 +0000 Subject: Improve error handling for missing library collection provided by the compiler --- src/build_settings.cpp | 26 ++++++++++++++++---------- src/checker.cpp | 4 ++-- src/main.cpp | 24 +++++++++++++++++------- src/parser.cpp | 15 ++++++++++++--- 4 files changed, 47 insertions(+), 22 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 374ecbdfa..9a773f9d3 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -876,7 +876,7 @@ gb_internal String internal_odin_root_dir(void) { #include -gb_internal String path_to_fullpath(gbAllocator a, String s); +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); gb_internal String internal_odin_root_dir(void) { String path = global_module_path; @@ -930,7 +930,7 @@ gb_internal String internal_odin_root_dir(void) { // NOTE: Linux / Unix is unfinished and not tested very well. #include -gb_internal String path_to_fullpath(gbAllocator a, String s); +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); gb_internal String internal_odin_root_dir(void) { String path = global_module_path; @@ -1091,7 +1091,7 @@ gb_internal String internal_odin_root_dir(void) { gb_global BlockingMutex fullpath_mutex; #if defined(GB_SYSTEM_WINDOWS) -gb_internal String path_to_fullpath(gbAllocator a, String s) { +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { String result = {}; String16 string16 = string_to_string16(heap_allocator(), s); @@ -1117,7 +1117,9 @@ gb_internal String path_to_fullpath(gbAllocator a, String s) { result.text[i] = '/'; } } + if (ok_) *ok_ = true; } else { + if (ok_) *ok_ = false; mutex_unlock(&fullpath_mutex); } @@ -1129,7 +1131,11 @@ gb_internal String path_to_fullpath(gbAllocator a, String s) { mutex_lock(&fullpath_mutex); p = realpath(cast(char *)s.text, 0); mutex_unlock(&fullpath_mutex); - if(p == nullptr) return String{}; + if(p == nullptr) { + if (ok_) *ok_ = false; + return String{}; + } + if (ok_) *ok_ = true; return make_string_c(p); } #else @@ -1137,7 +1143,7 @@ gb_internal String path_to_fullpath(gbAllocator a, String s) { #endif -gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String path) { +gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String path, bool *ok_) { u8 *str = gb_alloc_array(heap_allocator(), u8, base_dir.len+1+path.len+1); defer (gb_free(heap_allocator(), str)); @@ -1159,11 +1165,11 @@ gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } -gb_internal String get_fullpath_base_collection(gbAllocator a, String path) { +gb_internal String get_fullpath_base_collection(gbAllocator a, String path, bool *ok_) { String module_dir = odin_root_dir(); String base = str_lit("base/"); @@ -1180,10 +1186,10 @@ gb_internal String get_fullpath_base_collection(gbAllocator a, String path) { String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } -gb_internal String get_fullpath_core_collection(gbAllocator a, String path) { +gb_internal String get_fullpath_core_collection(gbAllocator a, String path, bool *ok_) { String module_dir = odin_root_dir(); String core = str_lit("core/"); @@ -1200,7 +1206,7 @@ gb_internal String get_fullpath_core_collection(gbAllocator a, String path) { String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } gb_internal bool show_error_line(void) { diff --git a/src/checker.cpp b/src/checker.cpp index e4a680a20..457ee6146 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -774,7 +774,7 @@ gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type * gb_internal AstPackage *get_runtime_package(CheckerInfo *info) { String name = str_lit("runtime"); gbAllocator a = heap_allocator(); - String path = get_fullpath_base_collection(a, name); + String path = get_fullpath_base_collection(a, name, nullptr); defer (gb_free(a, path.text)); auto found = string_map_get(&info->packages, path); if (found == nullptr) { @@ -795,7 +795,7 @@ gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { } gbAllocator a = heap_allocator(); - String path = get_fullpath_core_collection(a, name); + String path = get_fullpath_core_collection(a, name, nullptr); defer (gb_free(a, path.text)); auto found = string_map_get(&info->packages, path); if (found == nullptr) { diff --git a/src/main.cpp b/src/main.cpp index 1136db62a..7951ca2db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -807,9 +807,10 @@ gb_internal bool parse_build_flags(Array args) { } gbAllocator a = heap_allocator(); - String fullpath = path_to_fullpath(a, path); - if (!path_is_directory(fullpath)) { - gb_printf_err("Library collection '%.*s' path must be a directory, got '%.*s'\n", LIT(name), LIT(fullpath)); + bool path_ok = false; + String fullpath = path_to_fullpath(a, path, &path_ok); + if (!path_ok || !path_is_directory(fullpath)) { + gb_printf_err("Library collection '%.*s' path must be a directory, got '%.*s'\n", LIT(name), LIT(path_ok ? fullpath : path)); gb_free(a, fullpath.text); bad_flags = true; break; @@ -2395,9 +2396,18 @@ int main(int arg_count, char const **arg_ptr) { TIME_SECTION("init default library collections"); array_init(&library_collections, heap_allocator()); // NOTE(bill): 'core' cannot be (re)defined by the user - add_library_collection(str_lit("base"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("base"))); - add_library_collection(str_lit("core"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("core"))); - add_library_collection(str_lit("vendor"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("vendor"))); + + auto const &add_collection = [](String const &name) { + bool ok = false; + add_library_collection(name, get_fullpath_relative(heap_allocator(), odin_root_dir(), name, &ok)); + if (!ok) { + compiler_error("Cannot find the library collection '%.*s'. Is the ODIN_ROOT set up correctly?", LIT(name)); + } + }; + + add_collection(str_lit("base")); + add_collection(str_lit("core")); + add_collection(str_lit("vendor")); TIME_SECTION("init args"); map_init(&build_context.defined_values); @@ -2581,7 +2591,7 @@ int main(int arg_count, char const **arg_ptr) { // NOTE(bill): add 'shared' directory if it is not already set if (!find_library_collection_path(str_lit("shared"), nullptr)) { add_library_collection(str_lit("shared"), - get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"))); + get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"), nullptr)); } init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr, selected_subtarget); diff --git a/src/parser.cpp b/src/parser.cpp index 48f2f8617..2a7f41b36 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5519,7 +5519,8 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (has_windows_drive) { *path = file_str; } else { - String fullpath = string_trim_whitespace(get_fullpath_relative(permanent_allocator(), base_dir, file_str)); + bool ok = false; + String fullpath = string_trim_whitespace(get_fullpath_relative(permanent_allocator(), base_dir, file_str, &ok)); *path = fullpath; } return true; @@ -6141,7 +6142,11 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { { // Add these packages serially and then process them parallel TokenPos init_pos = {}; { - String s = get_fullpath_base_collection(permanent_allocator(), str_lit("runtime")); + bool ok = false; + String s = get_fullpath_base_collection(permanent_allocator(), str_lit("runtime"), &ok); + if (!ok) { + compiler_error("Unable to find The 'base:runtime' package. Is the ODIN_ROOT set up correctly?"); + } try_add_import_path(p, s, s, init_pos, Package_Runtime); } @@ -6149,7 +6154,11 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { p->init_fullpath = init_fullpath; if (build_context.command_kind == Command_test) { - String s = get_fullpath_core_collection(permanent_allocator(), str_lit("testing")); + bool ok = false; + String s = get_fullpath_core_collection(permanent_allocator(), str_lit("testing"), &ok); + if (!ok) { + compiler_error("Unable to find The 'core:testing' package. Is the ODIN_ROOT set up correctly?"); + } try_add_import_path(p, s, s, init_pos, Package_Normal); } -- cgit v1.2.3 From a8c4f4674714e88f1d81192835f095ff0d282f45 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 7 Feb 2024 19:51:00 +0100 Subject: fix ci --- src/build_settings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 9a773f9d3..0bcb9f298 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -907,7 +907,7 @@ gb_internal String internal_odin_root_dir(void) { text = gb_alloc_array(permanent_allocator(), u8, len + 1); gb_memmove(text, &path_buf[0], len); - path = path_to_fullpath(heap_allocator(), make_string(text, len)); + path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; @@ -1072,7 +1072,7 @@ gb_internal String internal_odin_root_dir(void) { gb_memmove(text, &path_buf[0], len); - path = path_to_fullpath(heap_allocator(), make_string(text, len)); + path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; if (c == '/' || c == '\\') { @@ -1126,7 +1126,7 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { return result; } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) -gb_internal String path_to_fullpath(gbAllocator a, String s) { +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { char *p; mutex_lock(&fullpath_mutex); p = realpath(cast(char *)s.text, 0); -- cgit v1.2.3 From 88add0b6b12b6590fd69bb74182f1a7689ae9ff6 Mon Sep 17 00:00:00 2001 From: avanspector Date: Sun, 25 Feb 2024 02:24:52 +0100 Subject: Improve Haiku support --- src/build_settings.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/check_builtin.cpp | 1 + src/checker.cpp | 1 + src/linker.cpp | 4 +-- src/llvm_backend.cpp | 4 +-- src/tilde.cpp | 1 + 6 files changed, 73 insertions(+), 4 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 0bcb9f298..f395cb515 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -18,6 +18,7 @@ enum TargetOsKind : u16 { TargetOs_essence, TargetOs_freebsd, TargetOs_openbsd, + TargetOs_haiku, TargetOs_wasi, TargetOs_js, @@ -542,6 +543,13 @@ gb_global TargetMetrics target_openbsd_amd64 = { str_lit("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"), }; +gb_global TargetMetrics target_haiku_amd64 = { + TargetOs_haiku, + TargetArch_amd64, + 8, 8, 8, 16, + str_lit("x86_64-unknown-haiku"), +}; + gb_global TargetMetrics target_essence_amd64 = { TargetOs_essence, TargetArch_amd64, @@ -641,6 +649,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, + { str_lit("haiku_amd64"), &target_haiku_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, { str_lit("wasi_wasm32"), &target_wasi_wasm32 }, @@ -872,6 +881,58 @@ gb_internal String internal_odin_root_dir(void) { return path; } +#elif defined(GB_SYSTEM_HAIKU) + +#include + +gb_internal String internal_odin_root_dir(void) { + String path = global_module_path; + isize len, i; + u8 *text; + + if (global_module_path_set) { + return global_module_path; + } + + auto path_buf = array_make(heap_allocator(), 300); + + len = 0; + for (;;) { + u32 sz = path_buf.count; + int res = find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, nullptr, &path_buf[0], sz); + if(res == B_OK) { + len = sz; + break; + } else { + array_resize(&path_buf, sz + 1); + } + } + + mutex_lock(&string_buffer_mutex); + defer (mutex_unlock(&string_buffer_mutex)); + + text = gb_alloc_array(permanent_allocator(), u8, len + 1); + gb_memmove(text, &path_buf[0], len); + + path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); + + for (i = path.len-1; i >= 0; i--) { + u8 c = path[i]; + if (c == '/' || c == '\\') { + break; + } + path.len--; + } + + global_module_path = path; + global_module_path_set = true; + + + // array_free(&path_buf); + + return path; +} + #elif defined(GB_SYSTEM_OSX) #include @@ -1301,6 +1362,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta metrics = &target_freebsd_amd64; #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; + #elif defined(GB_SYSTEM_HAIKU) + metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) metrics = &target_linux_arm64; #else @@ -1405,6 +1468,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta case TargetOs_openbsd: bc->link_flags = str_lit("-arch x86-64 "); break; + case TargetOs_haiku: + bc->link_flags = str_lit("-arch x86-64 "); + break; } } else if (bc->metrics.arch == TargetArch_i386) { switch (bc->metrics.os) { diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index d39be37a9..e00f6c053 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4928,6 +4928,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case TargetOs_essence: case TargetOs_freebsd: case TargetOs_openbsd: + case TargetOs_haiku: switch (build_context.metrics.arch) { case TargetArch_i386: case TargetArch_amd64: diff --git a/src/checker.cpp b/src/checker.cpp index 569a3c76f..b8b8e21e5 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1007,6 +1007,7 @@ gb_internal void init_universal(void) { {"Linux", TargetOs_linux}, {"Essence", TargetOs_essence}, {"FreeBSD", TargetOs_freebsd}, + {"Haiku", TargetOs_haiku}, {"OpenBSD", TargetOs_openbsd}, {"WASI", TargetOs_wasi}, {"JS", TargetOs_js}, diff --git a/src/linker.cpp b/src/linker.cpp index 987fab7f7..4e39f2ddc 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -474,8 +474,8 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); } - } else if (build_context.metrics.os != TargetOs_openbsd) { - // OpenBSD defaults to PIE executable. do not pass -no-pie for it. + } else if (build_context.metrics.os != TargetOs_openbsd && build_context.metrics.os != TargetOs_haiku) { + // OpenBSD and Haiku default to PIE executable. do not pass -no-pie for it. link_settings = gb_string_appendc(link_settings, "-no-pie "); } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index fa76ac22f..01d7a23b2 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2602,8 +2602,8 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { switch (build_context.reloc_mode) { case RelocMode_Default: - if (build_context.metrics.os == TargetOs_openbsd) { - // Always use PIC for OpenBSD: it defaults to PIE + if (build_context.metrics.os == TargetOs_openbsd || build_context.metrics.os == TargetOs_haiku) { + // Always use PIC for OpenBSD and Haiku: they default to PIE reloc_mode = LLVMRelocPIC; } break; diff --git a/src/tilde.cpp b/src/tilde.cpp index 06428f317..4fc7d1c9b 100644 --- a/src/tilde.cpp +++ b/src/tilde.cpp @@ -825,6 +825,7 @@ gb_internal bool cg_generate_code(Checker *c, LinkerData *linker_data) { case TargetOs_essence: case TargetOs_freebsd: case TargetOs_openbsd: + case TargetOs_haiku: debug_format = TB_DEBUGFMT_DWARF; break; } -- cgit v1.2.3 From 24c8b1540920bd181dc399bf86f2ec3a8ea72762 Mon Sep 17 00:00:00 2001 From: avanspector Date: Sun, 25 Feb 2024 02:38:35 +0100 Subject: small fixes --- src/build_settings.cpp | 2 ++ src/threading.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index f395cb515..e4e360270 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -885,6 +885,8 @@ gb_internal String internal_odin_root_dir(void) { #include +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); + gb_internal String internal_odin_root_dir(void) { String path = global_module_path; isize len, i; diff --git a/src/threading.cpp b/src/threading.cpp index 6602bf67e..56f246955 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -915,7 +915,7 @@ void futex_wait(Futex *f, Footex val) { waitq->lock(); auto head = &waitq->list; - Wait_Node waiter; + Futex_Wait_Node waiter; waiter.thread = pthread_self(); waiter.futex = f; waiter.prev = head; -- cgit v1.2.3 From 0fa6ba726f4debe888f2c5626547997b4ef541f8 Mon Sep 17 00:00:00 2001 From: avanspector Date: Sun, 25 Feb 2024 03:03:12 +0100 Subject: add haiku build token --- src/build_settings.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index e4e360270..f66cd278a 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -79,6 +79,7 @@ gb_global String target_os_names[TargetOs_COUNT] = { str_lit("essence"), str_lit("freebsd"), str_lit("openbsd"), + str_lit("haiku"), str_lit("wasi"), str_lit("js"), -- cgit v1.2.3 From fca691a06619a543b43e1aefd65881f27f4c6678 Mon Sep 17 00:00:00 2001 From: avanspector Date: Tue, 27 Feb 2024 02:38:06 +0100 Subject: fix core:thread and a memory leak in the future probably native non-pthread implementation for haiku will be required --- core/sys/unix/pthread_unix.odin | 18 +++++++++++------- core/thread/thread_unix.odin | 13 +++++++++---- src/build_settings.cpp | 8 ++------ 3 files changed, 22 insertions(+), 17 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 2759d5aae..4fe3c8dfa 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -33,15 +33,9 @@ foreign pthread { pthread_attr_getschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int --- pthread_attr_setschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int --- - pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int --- - pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int --- - // states: PTHREAD_CREATE_DETACHED, PTHREAD_CREATE_JOINABLE pthread_attr_setdetachstate :: proc(attrs: ^pthread_attr_t, detach_state: c.int) -> c.int --- - - // scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED - pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int --- - + // NOTE(tetra, 2019-11-06): WARNING: Different systems have different alignment requirements. // For maximum usefulness, use the OS's page size. // ALSO VERY MAJOR WARNING: `stack_ptr` must be the LAST byte of the stack on systems @@ -57,7 +51,17 @@ foreign pthread { pthread_sigmask :: proc(how: c.int, set: rawptr, oldset: rawptr) -> c.int --- sched_yield :: proc() -> c.int --- +} + +// NOTE: Unimplemented in Haiku. +when ODIN_OS != .Haiku { + foreign pthread { + // scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED + pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int --- + pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int --- + pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int --- + } } @(default_calling_convention="c") diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 19e421646..c75710873 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,4 +1,4 @@ -// +build linux, darwin, freebsd, openbsd +// +build linux, darwin, freebsd, openbsd, haiku // +private package thread @@ -78,7 +78,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0) - assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) + when ODIN_OS != .Haiku { + assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) + } thread := new(Thread) if thread == nil { @@ -88,8 +90,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Set thread priority. policy: i32 - res := unix.pthread_attr_getschedpolicy(&attrs, &policy) - assert(res == 0) + res: i32 + when ODIN_OS != .Haiku { + res = unix.pthread_attr_getschedpolicy(&attrs, &policy) + assert(res == 0) + } params: unix.sched_param res = unix.pthread_attr_getschedparam(&attrs, ¶ms) assert(res == 0) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index f66cd278a..fdaa971f1 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -898,6 +898,7 @@ gb_internal String internal_odin_root_dir(void) { } auto path_buf = array_make(heap_allocator(), 300); + defer (array_free(&path_buf)); len = 0; for (;;) { @@ -930,9 +931,6 @@ gb_internal String internal_odin_root_dir(void) { global_module_path = path; global_module_path_set = true; - - // array_free(&path_buf); - return path; } @@ -952,6 +950,7 @@ gb_internal String internal_odin_root_dir(void) { } auto path_buf = array_make(heap_allocator(), 300); + defer (array_free(&path_buf)); len = 0; for (;;) { @@ -984,9 +983,6 @@ gb_internal String internal_odin_root_dir(void) { global_module_path = path; global_module_path_set = true; - - // array_free(&path_buf); - return path; } #else -- cgit v1.2.3 From 9a2fc6cf4c8b4434ae45170953b77b3239120fea Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 15:34:29 +0000 Subject: Serialize errors to make them sortable, deterministic, and generally more control --- src/array.cpp | 7 ++ src/build_settings.cpp | 4 + src/check_builtin.cpp | 2 +- src/check_expr.cpp | 2 +- src/checker.cpp | 8 +- src/common.cpp | 2 +- src/docs.cpp | 4 +- src/docs_writer.cpp | 2 +- src/error.cpp | 213 ++++++++++++++++++++++++++++++------------------- src/llvm_backend.cpp | 2 +- src/main.cpp | 4 +- src/string.cpp | 1 - 12 files changed, 158 insertions(+), 93 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/array.cpp b/src/array.cpp index 4583a31a9..ec2c97d0e 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -51,6 +51,13 @@ template gb_internal void array_copy(Array *array, Array cons template gb_internal T *array_end_ptr(Array *array); +template +gb_internal void array_sort(Array &array, gbCompareProc compare_proc) { + gb_sort_array(array.data, array.count, compare_proc); +} + + + template struct Slice { T *data; diff --git a/src/build_settings.cpp b/src/build_settings.cpp index fdaa971f1..c4073f329 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1272,6 +1272,10 @@ gb_internal String get_fullpath_core_collection(gbAllocator a, String path, bool gb_internal bool show_error_line(void) { return !build_context.hide_error_line; } + +gb_internal bool terse_errors(void) { + return build_context.terse_errors; +} gb_internal bool has_ansi_terminal_colours(void) { return build_context.has_ansi_terminal_colours; } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index e1b1cd693..6de3b27f2 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1389,7 +1389,7 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c } } - gb_sort_array(file_caches.data, file_caches.count, file_cache_sort_cmp); + array_sort(file_caches, file_cache_sort_cmp); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 236d44a43..f359d5a54 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6485,7 +6485,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, } if (valids.count > 1) { - gb_sort_array(valids.data, valids.count, valid_index_and_score_cmp); + array_sort(valids, valid_index_and_score_cmp); i64 best_score = valids[0].score; Entity *best_entity = proc_entities[valids[0].index]; GB_ASSERT(best_entity != nullptr); diff --git a/src/checker.cpp b/src/checker.cpp index fb7d401ab..836f803fc 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -5044,7 +5044,7 @@ gb_internal void check_create_file_scopes(Checker *c) { for_array(i, c->parser->packages) { AstPackage *pkg = c->parser->packages[i]; - gb_sort_array(pkg->files.data, pkg->files.count, sort_file_by_name); + array_sort(pkg->files, sort_file_by_name); isize total_pkg_decl_count = 0; for_array(j, pkg->files) { @@ -5673,7 +5673,7 @@ gb_internal void remove_neighbouring_duplicate_entires_from_sorted_array(Arrayinfo.testing_procedures.data, c->info.testing_procedures.count, init_procedures_cmp); + array_sort(c->info.testing_procedures, init_procedures_cmp); remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.testing_procedures); if (build_context.test_names.entries.count == 0) { @@ -6122,8 +6122,8 @@ gb_internal GB_COMPARE_PROC(fini_procedures_cmp) { } gb_internal void check_sort_init_and_fini_procedures(Checker *c) { - gb_sort_array(c->info.init_procedures.data, c->info.init_procedures.count, init_procedures_cmp); - gb_sort_array(c->info.fini_procedures.data, c->info.fini_procedures.count, fini_procedures_cmp); + array_sort(c->info.init_procedures, init_procedures_cmp); + array_sort(c->info.fini_procedures, fini_procedures_cmp); // NOTE(bill): remove possible duplicates from the init/fini lists // NOTE(bill): because the arrays are sorted, you only need to check the previous element diff --git a/src/common.cpp b/src/common.cpp index 90632def3..aad420325 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -913,7 +913,7 @@ gb_internal void did_you_mean_append(DidYouMeanAnswers *d, String const &target) array_add(&d->distances, dat); } gb_internal Slice did_you_mean_results(DidYouMeanAnswers *d) { - gb_sort_array(d->distances.data, d->distances.count, gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance))); + array_sort(d->distances, gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance))); isize count = 0; for (isize i = 0; i < d->distances.count; i++) { isize distance = d->distances[i].distance; diff --git a/src/docs.cpp b/src/docs.cpp index f00d4e15a..004134a5c 100644 --- a/src/docs.cpp +++ b/src/docs.cpp @@ -237,7 +237,7 @@ gb_internal void print_doc_package(CheckerInfo *info, AstPackage *pkg) { } array_add(&entities, e); } - gb_sort_array(entities.data, entities.count, cmp_entities_for_printing); + array_sort(entities, cmp_entities_for_printing); bool show_docs = (build_context.cmd_doc_flags & CmdDocFlag_Short) == 0; @@ -358,7 +358,7 @@ gb_internal void generate_documentation(Checker *c) { } } - gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name); + array_sort(pkgs, cmp_ast_package_by_name); for_array(i, pkgs) { print_doc_package(info, pkgs[i]); diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 1bc244918..26d8027a9 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -1107,7 +1107,7 @@ gb_internal void odin_doc_write_docs(OdinDocWriter *w) { } debugf("odin_doc_update_entities sort pkgs %s\n", w->state ? "preparing" : "writing"); - gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name); + array_sort(pkgs, cmp_ast_package_by_name); for_array(i, pkgs) { gbAllocator allocator = heap_allocator(); diff --git a/src/error.cpp b/src/error.cpp index e63682829..e5803e5a2 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -1,3 +1,14 @@ +enum ErrorValueKind : u32 { + ErrorValue_Error, + ErrorValue_Warning, +}; + +struct ErrorValue { + ErrorValueKind kind; + TokenPos pos; + Array msgs; +}; + struct ErrorCollector { TokenPos prev; std::atomic count; @@ -8,21 +19,54 @@ struct ErrorCollector { BlockingMutex string_mutex; RecursiveMutex block_mutex; - RecursiveMutex error_buffer_mutex; - Array error_buffer; - Array errors; + Array error_values; + ErrorValue curr_error_value; + std::atomic curr_error_value_set; }; gb_global ErrorCollector global_error_collector; +gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) { + GB_ASSERT(global_error_collector.curr_error_value_set.load() == false); + ErrorValue ev = {kind, pos}; + ev.msgs.allocator = heap_allocator(); + + global_error_collector.curr_error_value = ev; + global_error_collector.curr_error_value_set.store(true); +} + +gb_internal void pop_error_value(void) { + if (global_error_collector.curr_error_value_set.load()) { + array_add(&global_error_collector.error_values, global_error_collector.curr_error_value); + + global_error_collector.curr_error_value = {}; + global_error_collector.curr_error_value_set.store(false); + } +} + + +gb_internal void try_pop_error_value(void) { + if (!global_error_collector.in_block.load()) { + pop_error_value(); + } +} + +gb_internal ErrorValue *get_error_value(void) { + GB_ASSERT(global_error_collector.curr_error_value_set.load() == true); + return &global_error_collector.curr_error_value; +} + + + gb_internal bool any_errors(void) { return global_error_collector.count.load() != 0; } + + gb_internal void init_global_error_collector(void) { - array_init(&global_error_collector.errors, heap_allocator()); - array_init(&global_error_collector.error_buffer, heap_allocator()); + array_init(&global_error_collector.error_values, heap_allocator()); array_init(&global_file_path_strings, heap_allocator(), 1, 4096); array_init(&global_files, heap_allocator(), 1, 4096); } @@ -102,6 +146,7 @@ gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { gb_internal bool global_warnings_as_errors(void); gb_internal bool global_ignore_warnings(void); gb_internal bool show_error_line(void); +gb_internal bool terse_errors(void); gb_internal bool has_ansi_terminal_colours(void); gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset); @@ -113,55 +158,32 @@ gb_internal void syntax_error(Token const &token, char const *fmt, ...); gb_internal void syntax_error(TokenPos pos, char const *fmt, ...); gb_internal void syntax_warning(Token const &token, char const *fmt, ...); gb_internal void compiler_error(char const *fmt, ...); +gb_internal void print_all_errors(void); -gb_internal void begin_error_block(void) { - mutex_lock(&global_error_collector.block_mutex); - global_error_collector.in_block.store(true); -} -gb_internal void end_error_block(void) { - mutex_lock(&global_error_collector.error_buffer_mutex); - isize n = global_error_collector.error_buffer.count; - if (n > 0) { - u8 *text = global_error_collector.error_buffer.data; - - bool add_extra_newline = false; +#define ERROR_OUT_PROC(name) void name(char const *fmt, va_list va) +typedef ERROR_OUT_PROC(ErrorOutProc); - if (show_error_line()) { - if (n >= 2 && !(text[n-2] == '\n' && text[n-1] == '\n')) { - add_extra_newline = true; - } - } else { - isize newline_count = 0; - for (isize i = 0; i < n; i++) { - if (text[i] == '\n') { - newline_count += 1; - } - } - if (newline_count > 1) { - add_extra_newline = true; - } - } +gb_internal ERROR_OUT_PROC(default_error_out_va) { + char buf[4096] = {}; + isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); + isize n = len-1; - if (add_extra_newline) { - // add an extra new line as padding when the error line is being shown - error_line("\n"); - } + String msg = {(u8 *)buf, n}; - n = global_error_collector.error_buffer.count; - text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, global_error_collector.error_buffer.data, n); - text[n] = 0; + ErrorValue *ev = get_error_value(); + array_add(&ev->msgs, copy_string(permanent_allocator(), msg)); +} +gb_global ErrorOutProc *error_out_va = default_error_out_va; - mutex_lock(&global_error_collector.error_out_mutex); - String s = {text, n}; - array_add(&global_error_collector.errors, s); - mutex_unlock(&global_error_collector.error_out_mutex); +gb_internal void begin_error_block(void) { + mutex_lock(&global_error_collector.block_mutex); + global_error_collector.in_block.store(true); +} - global_error_collector.error_buffer.count = 0; - } - mutex_unlock(&global_error_collector.error_buffer_mutex); +gb_internal void end_error_block(void) { + pop_error_value(); global_error_collector.in_block.store(false); mutex_unlock(&global_error_collector.block_mutex); } @@ -169,40 +191,6 @@ gb_internal void end_error_block(void) { #define ERROR_BLOCK() begin_error_block(); defer (end_error_block()) -#define ERROR_OUT_PROC(name) void name(char const *fmt, va_list va) -typedef ERROR_OUT_PROC(ErrorOutProc); - -gb_internal ERROR_OUT_PROC(default_error_out_va) { - gbFile *f = gb_file_get_standard(gbFileStandard_Error); - - char buf[4096] = {}; - isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); - isize n = len-1; - if (global_error_collector.in_block) { - mutex_lock(&global_error_collector.error_buffer_mutex); - - isize cap = global_error_collector.error_buffer.count + n; - array_reserve(&global_error_collector.error_buffer, cap); - u8 *data = global_error_collector.error_buffer.data + global_error_collector.error_buffer.count; - gb_memmove(data, buf, n); - global_error_collector.error_buffer.count += n; - - mutex_unlock(&global_error_collector.error_buffer_mutex); - } else { - mutex_lock(&global_error_collector.error_out_mutex); - { - u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, buf, n); - text[n] = 0; - array_add(&global_error_collector.errors, make_string(text, n)); - } - mutex_unlock(&global_error_collector.error_out_mutex); - - } - gb_file_write(f, buf, n); -} - -gb_global ErrorOutProc *error_out_va = default_error_out_va; gb_internal void error_out(char const *fmt, ...) { va_list va; @@ -357,9 +345,12 @@ gb_internal void error_out_coloured(char const *str, TerminalStyle style, Termin gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Error); // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); @@ -377,6 +368,7 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va } else { global_error_collector.count.fetch_sub(1); } + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -387,6 +379,9 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, } global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Warning); + if (!global_ignore_warnings()) { // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { @@ -402,6 +397,7 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, show_error_on_line(pos, end); } } + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -413,9 +409,13 @@ gb_internal void error_line_va(char const *fmt, va_list va) { gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count.load() > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Error); + // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); @@ -428,6 +428,8 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li } error_out_va(fmt, va); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -435,9 +437,13 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Warning); + // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { global_error_collector.prev = pos; @@ -451,15 +457,21 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * error_out_va(fmt, va); error_out("\n"); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { + print_all_errors(); gb_exit(1); } mutex_lock(&global_error_collector.mutex); + + push_error_value(pos, ErrorValue_Warning); + // NOTE(bill): Duplicate error, skip it if (pos.line == 0) { error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); @@ -475,6 +487,8 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, error_out("\n"); show_error_on_line(pos, end); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -486,6 +500,10 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const } mutex_lock(&global_error_collector.mutex); global_error_collector.warning_count++; + + + push_error_value(pos, ErrorValue_Warning); + if (!global_ignore_warnings()) { // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { @@ -501,6 +519,8 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const error_out("\n"); } } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -568,6 +588,8 @@ gb_internal void syntax_error_with_verbose(TokenPos pos, TokenPos end, char cons gb_internal void compiler_error(char const *fmt, ...) { + print_all_errors(); + va_list va; va_start(va, fmt); @@ -577,3 +599,34 @@ gb_internal void compiler_error(char const *fmt, ...) { GB_DEBUG_TRAP(); gb_exit(1); } + + + + + +gb_internal int error_value_cmp(void const *a, void const *b) { + ErrorValue *x = cast(ErrorValue *)a; + ErrorValue *y = cast(ErrorValue *)b; + return token_pos_cmp(x->pos, y->pos); +} + +gb_internal void print_all_errors(void) { + GB_ASSERT(any_errors()); + gbFile *f = gb_file_get_standard(gbFileStandard_Error); + + array_sort(global_error_collector.error_values, error_value_cmp); + + for_array(i, global_error_collector.error_values) { + ErrorValue ev = global_error_collector.error_values[i]; + for_array(j, ev.msgs) { + String msg = ev.msgs[j]; + gb_file_write(f, msg.text, msg.len); + if (terse_errors()) { + if (string_contains_char(msg, '\n')) { + break; + } + } + } + } + +} \ No newline at end of file diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ca4341525..b8ee7e7fa 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3021,7 +3021,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } - gb_sort_array(gen->foreign_libraries.data, gen->foreign_libraries.count, foreign_library_cmp); + array_sort(gen->foreign_libraries, foreign_library_cmp); return true; } diff --git a/src/main.cpp b/src/main.cpp index 7951ca2db..0f28e137f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2095,7 +2095,7 @@ gb_internal void print_show_unused(Checker *c) { array_add(&unused, e); } - gb_sort_array(unused.data, unused.count, cmp_entities_for_printing); + array_sort(unused, cmp_entities_for_printing); print_usage_line(0, "Unused Package Declarations"); @@ -2680,6 +2680,7 @@ int main(int arg_count, char const **arg_ptr) { } if (any_errors()) { + print_all_errors(); return 1; } @@ -2691,6 +2692,7 @@ int main(int arg_count, char const **arg_ptr) { check_parsed_files(checker); if (any_errors()) { + print_all_errors(); return 1; } diff --git a/src/string.cpp b/src/string.cpp index 8be40ec3c..7bfa52f33 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -89,7 +89,6 @@ gb_internal char *alloc_cstring(gbAllocator a, String s) { } - gb_internal gb_inline bool str_eq_ignore_case(String const &a, String const &b) { if (a.len == b.len) { for (isize i = 0; i < a.len; i++) { -- cgit v1.2.3 From ba428fecdb309846b9a6dc8a6a3d738f2034f2ff Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 16:25:09 +0000 Subject: Add `-json-errors` --- src/build_settings.cpp | 8 +++- src/error.cpp | 107 +++++++++++++++++++++++++++++++++++++++++++------ src/main.cpp | 10 +++++ 3 files changed, 110 insertions(+), 15 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c4073f329..58b5c9170 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -392,6 +392,7 @@ struct BuildContext { bool warnings_as_errors; bool hide_error_line; bool terse_errors; + bool json_errors; bool has_ansi_terminal_colours; bool ignore_lazy; @@ -1270,14 +1271,17 @@ gb_internal String get_fullpath_core_collection(gbAllocator a, String path, bool } gb_internal bool show_error_line(void) { - return !build_context.hide_error_line; + return !build_context.hide_error_line && !build_context.json_errors; } gb_internal bool terse_errors(void) { return build_context.terse_errors; } +gb_internal bool json_errors(void) { + return build_context.json_errors; +} gb_internal bool has_ansi_terminal_colours(void) { - return build_context.has_ansi_terminal_colours; + return build_context.has_ansi_terminal_colours && !json_errors(); } gb_internal bool has_asm_extension(String const &path) { diff --git a/src/error.cpp b/src/error.cpp index d116781fb..509470602 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -6,6 +6,7 @@ enum ErrorValueKind : u32 { struct ErrorValue { ErrorValueKind kind; TokenPos pos; + TokenPos end; Array msgs; }; @@ -146,6 +147,7 @@ gb_internal bool global_warnings_as_errors(void); gb_internal bool global_ignore_warnings(void); gb_internal bool show_error_line(void); gb_internal bool terse_errors(void); +gb_internal bool json_errors(void); gb_internal bool has_ansi_terminal_colours(void); gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset); @@ -168,13 +170,11 @@ gb_internal ERROR_OUT_PROC(default_error_out_va) { isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); isize n = len-1; - String msg = {}; - if (n) { - msg = copy_string(permanent_allocator(), {(u8 *)buf, n}); + if (n > 0) { + String msg = copy_string(permanent_allocator(), {(u8 *)buf, n}); + ErrorValue *ev = get_error_value(); + array_add(&ev->msgs, msg); } - - ErrorValue *ev = get_error_value(); - array_add(&ev->msgs, msg); } gb_global ErrorOutProc *error_out_va = default_error_out_va; @@ -246,6 +246,7 @@ gb_internal void terminal_reset_colours(void) { gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { + get_error_value()->end = end; if (!show_error_line()) { return false; } @@ -622,19 +623,99 @@ gb_internal int error_value_cmp(void const *a, void const *b) { } gb_internal void print_all_errors(void) { + auto const &escape_char = [](gbFile *f, u8 c) { + switch (c) { + case '\n': gb_file_write(f, "\\n", 2); break; + case '"': gb_file_write(f, "\\\"", 2); break; + case '\\': gb_file_write(f, "\\\\", 2); break; + case '\b': gb_file_write(f, "\\b", 2); break; + case '\f': gb_file_write(f, "\\f", 2); break; + case '\r': gb_file_write(f, "\\r", 2); break; + case '\t': gb_file_write(f, "\\t", 2); break; + default: + if ('\x00' <= c && c <= '\x1f') { + gb_fprintf(f, "\\u%04x", c); + } else { + gb_file_write(f, &c, 1); + } + break; + } + }; + GB_ASSERT(any_errors()); gbFile *f = gb_file_get_standard(gbFileStandard_Error); array_sort(global_error_collector.error_values, error_value_cmp); - for_array(i, global_error_collector.error_values) { - ErrorValue ev = global_error_collector.error_values[i]; - for (isize j = 0; j < ev.msgs.count; j++) { - String msg = ev.msgs[j]; - gb_file_write(f, msg.text, msg.len); - if (terse_errors() && string_contains_char(msg, '\n')) { - break; + if (json_errors()) { + gb_fprintf(f, "{\n"); + gb_fprintf(f, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); + gb_fprintf(f, "\t\"errors\": [\n"); + for_array(i, global_error_collector.error_values) { + ErrorValue ev = global_error_collector.error_values[i]; + + gb_fprintf(f, "\t\t{\n"); + + gb_fprintf(f, "\t\t\t\"pos\": {\n"); + + if (ev.pos.file_id) { + gb_fprintf(f, "\t\t\t\t\"file\": \""); + String file = get_file_path_string(ev.pos.file_id); + for (isize k = 0; k < file.len; k++) { + escape_char(f, file.text[k]); + } + gb_fprintf(f, "\",\n"); + gb_fprintf(f, "\t\t\t\t\"line\": %d,\n", ev.pos.line); + gb_fprintf(f, "\t\t\t\t\"column\": %d,\n", ev.pos.column); + i32 end_column = gb_max(ev.end.column, ev.pos.column); + gb_fprintf(f, "\t\t\t\t\"end_column\": %d\n", end_column); + gb_fprintf(f, "\t\t\t},\n"); + } + + gb_fprintf(f, "\t\t\t\"msgs\": [\n"); + + if (ev.msgs.count > 1) { + gb_fprintf(f, "\t\t\t\t\""); + + for (isize j = 1; j < ev.msgs.count; j++) { + String msg = ev.msgs[j]; + for (isize k = 0; k < msg.len; k++) { + u8 c = msg.text[k]; + if (c == '\n') { + if (k+1 == msg.len && j+1 == ev.msgs.count) { + // don't do the last one + } else { + gb_fprintf(f, "\",\n"); + gb_fprintf(f, "\t\t\t\t\""); + } + } else { + escape_char(f, c); + } + } + } + gb_fprintf(f, "\"\n"); + } + gb_fprintf(f, "\t\t\t]\n"); + gb_fprintf(f, "\t\t}"); + if (i+1 != global_error_collector.error_values.count) { + gb_fprintf(f, ","); + } + gb_fprintf(f, "\n"); + } + + gb_fprintf(f, "\t]\n"); + gb_fprintf(f, "}\n"); + } else { + for_array(i, global_error_collector.error_values) { + ErrorValue ev = global_error_collector.error_values[i]; + for (isize j = 0; j < ev.msgs.count; j++) { + String msg = ev.msgs[j]; + gb_file_write(f, msg.text, msg.len); + + if (terse_errors() && string_contains_char(msg, '\n')) { + break; + } } } } diff --git a/src/main.cpp b/src/main.cpp index 0f28e137f..672a9318e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -292,6 +292,7 @@ enum BuildFlagKind { BuildFlag_WarningsAsErrors, BuildFlag_TerseErrors, BuildFlag_VerboseErrors, + BuildFlag_JsonErrors, BuildFlag_ErrorPosStyle, BuildFlag_MaxErrorCount, @@ -480,6 +481,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_TerseErrors, str_lit("terse-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_VerboseErrors, str_lit("verbose-errors"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_JsonErrors, str_lit("json-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_ErrorPosStyle, str_lit("error-pos-style"), BuildFlagParam_String, Command_all); add_flag(&build_flags, BuildFlag_MaxErrorCount, str_lit("max-error-count"), BuildFlagParam_Integer, Command_all); @@ -1184,6 +1186,10 @@ gb_internal bool parse_build_flags(Array args) { build_context.terse_errors = false; break; + case BuildFlag_JsonErrors: + build_context.json_errors = true; + break; + case BuildFlag_ErrorPosStyle: GB_ASSERT(value.kind == ExactValue_String); @@ -1984,6 +1990,10 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Prints a terse error message without showing the code on that line and the location in that line."); print_usage_line(0, ""); + print_usage_line(1, "-json-errors"); + print_usage_line(2, "Prints the error messages as json to stderr."); + print_usage_line(0, ""); + print_usage_line(1, "-error-pos-style:"); print_usage_line(2, "Available options:"); print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); -- cgit v1.2.3 From a5558625228045830b0e90b3922f684350ff9beb Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 19 Mar 2024 19:43:41 +0100 Subject: linker improvements `path_to_fullpath` did different things on Windows&Unix, an attempt to bring them closer together was made here. This was prompted by the compiler completely ignoring `foreign import "foo.dylib"` when `foo.dylib` does not exist (because `path_to_fullpath` returns an empty string). Causing just unresolved symbol errors, when on Windows it would pass along the path to the linker and actually say it doesn't exist, which is now also the case for Unix. This also fixes some checker errors that relied on the Windows behaviour, for example: `Error: File name, , cannot be as a library name as it is not a valid identifier`. Made `-no-crt` require either `-default-to-nil-allocator` or `-default-to-panic-allocator` on Unix, the current default allocators rely on libc and this was not obvious and would immediately bring up unresolved symbol errors for the linked memory management functions, or just link with libc anyways because it was foreign imported. Added a suggestion to install `nasm` with the user's package manager when assembling using `nasm` fails on Unix, I saw some confusion about it in the Discord. Ignore explicit foreign imports of libc. It is already linked in later on in the linking process and would otherwise (at least on macOS) cause linker warnings for duplicate libraries. This also makes it so when using `-no-crt` and importing something that requires libc, linker errors are given (like I would expect), instead of silently still linking with libc because it was foreign imported. --- src/build_settings.cpp | 27 +++++++++++++++++++++++++-- src/linker.cpp | 14 +++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index fdaa971f1..fa57e3809 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1190,13 +1190,24 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { char *p; mutex_lock(&fullpath_mutex); p = realpath(cast(char *)s.text, 0); + defer (free(p)); mutex_unlock(&fullpath_mutex); if(p == nullptr) { if (ok_) *ok_ = false; - return String{}; + + // Path doesn't exist or is malformed, Windows's `GetFullPathNameW` does not check for + // existence of the file where `realpath` does, which causes different behaviour between platforms. + // Two things could be done here: + // 1. clean the path and resolve it manually, just like the Windows function does, + // probably requires porting `filepath.clean` from Odin and doing some more processing. + // 2. just return a copy of the original path. + // + // I have opted for 2 because it is much simpler + we already return `ok = false` + further + // checks and processes will use the path and cause errors (which we want). + return copy_string(a, s); } if (ok_) *ok_ = true; - return make_string_c(p); + return copy_string(a, make_string_c(p)); } #else #error Implement system @@ -1939,6 +1950,18 @@ gb_internal bool init_build_paths(String init_filename) { } } + if (build_context.no_crt && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR && !build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + switch (build_context.metrics.os) { + case TargetOs_linux: + case TargetOs_darwin: + case TargetOs_essence: + case TargetOs_freebsd: + case TargetOs_openbsd: + case TargetOs_haiku: + gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n"); + return false; + } + } if (bc->target_features_string.len != 0) { enable_target_feature({}, bc->target_features_string); diff --git a/src/linker.cpp b/src/linker.cpp index 0cdeaf8d9..9d7f16f1d 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -376,6 +376,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { LIT(obj_file), LIT(build_context.extra_assembler_flags) ); + if (!result) { + gb_printf_err("executing `nasm` to assemble foreing import of %.*s failed.\n\tSuggestion: `nasm` does not ship with the compiler and should be installed with your system's package manager.\n", LIT(asm_file)); + return result; + } } array_add(&gen->output_object_paths, obj_file); } else { @@ -383,9 +387,13 @@ gb_internal i32 linker_stage(LinkerData *gen) { continue; } - // NOTE(zangent): Sometimes, you have to use -framework on MacOS. - // This allows you to specify '-f' in a #foreign_system_library, - // without having to implement any new syntax specifically for MacOS. + // Do not add libc again, this is added later already, and omitted with + // the `-no-crt` flag, not skipping here would cause duplicate library + // warnings when linking on darwin and might link libc silently even with `-no-crt`. + if (lib == str_lit("System.framework") || lib == str_lit("c")) { + continue; + } + if (build_context.metrics.os == TargetOs_darwin) { if (string_ends_with(lib, str_lit(".framework"))) { // framework thingie -- cgit v1.2.3 From f312adb26aab6b3e0fdbaa7f9d1462a00daa474c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 20 Mar 2024 17:44:35 +0100 Subject: darwin: allow cross linking darwin_amd64 from darwin_arm64 --- src/build_settings.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3c822e295..4be189cf1 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1476,6 +1476,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->link_flags = str_lit("/machine:x64 "); break; case TargetOs_darwin: + bc->link_flags = str_lit("-arch x86_64 "); break; case TargetOs_linux: bc->link_flags = str_lit("-arch x86-64 "); -- cgit v1.2.3 From 15c1e8274dc19735ca9857962424846148596d26 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 23 Mar 2024 22:12:05 +0100 Subject: darwin: fix ld warnings and set minimum os version --- src/build_settings.cpp | 34 ++++++++++++++++++++-------------- src/linker.cpp | 5 +++-- src/main.cpp | 2 +- 3 files changed, 24 insertions(+), 17 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4be189cf1..19006ab2c 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -510,7 +510,7 @@ gb_global TargetMetrics target_darwin_amd64 = { TargetOs_darwin, TargetArch_amd64, 8, 8, 8, 16, - str_lit("x86_64-apple-darwin"), + str_lit("x86_64-apple-macosx"), // NOTE: Changes during initialization based on build flags. str_lit("e-m:o-i64:64-f80:128-n8:16:32:64-S128"), }; @@ -518,7 +518,7 @@ gb_global TargetMetrics target_darwin_arm64 = { TargetOs_darwin, TargetArch_arm64, 8, 8, 8, 16, - str_lit("arm64-apple-macosx11.0.0"), + str_lit("arm64-apple-macosx"), // NOTE: Changes during initialization based on build flags. str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), }; @@ -1418,19 +1418,25 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } bc->metrics = *metrics; - switch (subtarget) { - case Subtarget_Default: - break; - case Subtarget_iOS: - GB_ASSERT(metrics->os == TargetOs_darwin); - if (metrics->arch == TargetArch_arm64) { - bc->metrics.target_triplet = str_lit("arm64-apple-ios"); - } else if (metrics->arch == TargetArch_amd64) { - bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); - } else { - GB_PANIC("Unknown architecture for darwin"); + if (metrics->os == TargetOs_darwin) { + if (bc->minimum_os_version_string.len == 0) { + bc->minimum_os_version_string = str_lit("11.0.0"); + } + + switch (subtarget) { + case Subtarget_Default: + bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string); + break; + case Subtarget_iOS: + if (metrics->arch == TargetArch_arm64) { + bc->metrics.target_triplet = str_lit("arm64-apple-ios"); + } else if (metrics->arch == TargetArch_amd64) { + bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); + } else { + GB_PANIC("Unknown architecture for darwin"); + } + break; } - break; } bc->ODIN_OS = target_os_names[metrics->os]; diff --git a/src/linker.cpp b/src/linker.cpp index 9d7f16f1d..6699c9cb8 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -502,7 +502,6 @@ gb_internal i32 linker_stage(LinkerData *gen) { platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/local/lib "); } - // This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit. if (build_context.minimum_os_version_string.len) { link_settings = gb_string_append_fmt(link_settings, "-mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string)); } @@ -513,7 +512,9 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (!build_context.no_crt) { platform_lib_str = gb_string_appendc(platform_lib_str, "-lm "); if (build_context.metrics.os == TargetOs_darwin) { - platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem "); + // NOTE: adding this causes a warning about duplicate libraries, I think it is + // automatically assumed/added by clang when you don't do `-nostdlib`. + // platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem "); } else { platform_lib_str = gb_string_appendc(platform_lib_str, "-lc "); } diff --git a/src/main.cpp b/src/main.cpp index ab721a143..9c353653f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1925,7 +1925,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { if (run_or_build) { print_usage_line(1, "-minimum-os-version:"); print_usage_line(2, "Sets the minimum OS version targeted by the application."); - print_usage_line(2, "Example: -minimum-os-version:12.0.0"); + print_usage_line(2, "Default: -minimum-os-version:11.0.0"); print_usage_line(2, "(Only used when target is Darwin.)"); print_usage_line(0, ""); -- cgit v1.2.3 From b26a685b769c99128f96a4b00c578775daec5192 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 25 Mar 2024 21:55:58 +0100 Subject: darwin: be less annoying about "incompatible" library versions After #3316 we set a default minimum version, now this will warn if you link with a library that is targeting later versions. This might be a bit annoying, especially when the user hasn't actually given Odin a minimum target. So this PR makes these warnings only show when you explicitly give a target version (afaik that is the only thing that -mmacosx-min-version actually does for us because we don't use it to compile anything, just to link). --- src/build_settings.cpp | 4 +++- src/linker.cpp | 5 ++++- src/main.cpp | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 19006ab2c..3a9951cb2 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -436,7 +436,9 @@ struct BuildContext { BlockingMutex target_features_mutex; StringSet target_features_set; String target_features_string; + String minimum_os_version_string; + bool minimum_os_version_string_given; }; gb_global BuildContext build_context = {0}; @@ -1419,7 +1421,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->metrics = *metrics; if (metrics->os == TargetOs_darwin) { - if (bc->minimum_os_version_string.len == 0) { + if (!bc->minimum_os_version_string_given) { bc->minimum_os_version_string = str_lit("11.0.0"); } diff --git a/src/linker.cpp b/src/linker.cpp index 6699c9cb8..63987f9e8 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -502,9 +502,12 @@ gb_internal i32 linker_stage(LinkerData *gen) { platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/local/lib "); } - if (build_context.minimum_os_version_string.len) { + // Only specify this flag if the user has given a minimum version to target. + // This will cause warnings to show up for mismatched libraries. + if (build_context.minimum_os_version_string_given) { link_settings = gb_string_append_fmt(link_settings, "-mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string)); } + // This points the linker to where the entry point is link_settings = gb_string_appendc(link_settings, "-e _main "); } diff --git a/src/main.cpp b/src/main.cpp index 9c353653f..79c3a1670 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1066,6 +1066,7 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_MinimumOSVersion: { GB_ASSERT(value.kind == ExactValue_String); build_context.minimum_os_version_string = value.value_string; + build_context.minimum_os_version_string_given = true; break; } case BuildFlag_RelocMode: { @@ -1926,7 +1927,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-minimum-os-version:"); print_usage_line(2, "Sets the minimum OS version targeted by the application."); print_usage_line(2, "Default: -minimum-os-version:11.0.0"); - print_usage_line(2, "(Only used when target is Darwin.)"); + print_usage_line(2, "Only used when target is Darwin, if given, linking mismatched versions will emit a warning."); print_usage_line(0, ""); print_usage_line(1, "-extra-linker-flags:"); -- cgit v1.2.3 From 63f30a8207cf7729f4791f3d0429a428abc0ca4a Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 28 Mar 2024 18:44:21 +0100 Subject: speed up path_to_fullpath on Linux/MacOS We did some profiling for #3343 and this seems to be the biggest problem. `realpath` is expensive, and we are locking here for no reason that I can think of. This improves the "check procedure bodies" timing (of the linked issue) from 2.4s to .4s on my machine. --- src/build_settings.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3a9951cb2..f1a21161f 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1190,11 +1190,16 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { + static gb_thread_local StringMap cache; + + String* cached = string_map_get(&cache, s); + if (cached != nullptr) { + return copy_string(a, *cached); + } + char *p; - mutex_lock(&fullpath_mutex); p = realpath(cast(char *)s.text, 0); defer (free(p)); - mutex_unlock(&fullpath_mutex); if(p == nullptr) { if (ok_) *ok_ = false; @@ -1207,10 +1212,14 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { // // I have opted for 2 because it is much simpler + we already return `ok = false` + further // checks and processes will use the path and cause errors (which we want). - return copy_string(a, s); + String result = copy_string(a, s); + string_map_set(&cache, copy_string(permanent_allocator(), s), copy_string(permanent_allocator(), result)); + return result; } if (ok_) *ok_ = true; - return copy_string(a, make_string_c(p)); + String result = copy_string(a, make_string_c(p)); + string_map_set(&cache, copy_string(permanent_allocator(), s), copy_string(permanent_allocator(), result)); + return result; } #else #error Implement system -- cgit v1.2.3 From 9a5a39c07dd16997a434660450a6d62d9932f90f Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 28 Mar 2024 19:16:55 +0100 Subject: fix not setting ok in cached code path --- src/build_settings.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index f1a21161f..1ac9e451f 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1189,12 +1189,19 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { return result; } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) + +struct PathToFullpathResult { + String result; + bool ok; +}; + gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { - static gb_thread_local StringMap cache; + static gb_thread_local StringMap cache; - String* cached = string_map_get(&cache, s); + PathToFullpathResult *cached = string_map_get(&cache, s); if (cached != nullptr) { - return copy_string(a, *cached); + if (ok_) *ok_ = cached->ok; + return copy_string(a, cached->result); } char *p; @@ -1213,12 +1220,22 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { // I have opted for 2 because it is much simpler + we already return `ok = false` + further // checks and processes will use the path and cause errors (which we want). String result = copy_string(a, s); - string_map_set(&cache, copy_string(permanent_allocator(), s), copy_string(permanent_allocator(), result)); + + PathToFullpathResult cached_result = {}; + cached_result.result = copy_string(permanent_allocator(), result); + cached_result.ok = false; + string_map_set(&cache, copy_string(permanent_allocator(), s), cached_result); + return result; } if (ok_) *ok_ = true; String result = copy_string(a, make_string_c(p)); - string_map_set(&cache, copy_string(permanent_allocator(), s), copy_string(permanent_allocator(), result)); + + PathToFullpathResult cached_result = {}; + cached_result.result = copy_string(permanent_allocator(), result); + cached_result.ok = true; + string_map_set(&cache, copy_string(permanent_allocator(), s), cached_result); + return result; } #else -- cgit v1.2.3 From 0e5a482c42f28c58da18213b2b3257304f357476 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 4 Apr 2024 16:11:26 +0100 Subject: Default to "smart" linker behaviour; Add `-min-link-libs` to use minimize link libs if wanted --- src/build_settings.cpp | 4 +++- src/linker.cpp | 60 +++++++++++++++++++++++--------------------------- src/main.cpp | 13 +++++++++++ 3 files changed, 43 insertions(+), 34 deletions(-) (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 1ac9e451f..3b5d33ae3 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -412,7 +412,9 @@ struct BuildContext { bool dynamic_map_calls; - bool obfuscate_source_code_locations; + bool obfuscate_source_code_locations; + + bool min_link_libs; RelocMode reloc_mode; bool disable_red_zone; diff --git a/src/linker.cpp b/src/linker.cpp index 0e3169b22..2ff7ad0f4 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -139,9 +139,9 @@ gb_internal i32 linker_stage(LinkerData *gen) { } - StringSet libs = {}; - string_set_init(&libs, 64); - defer (string_set_destroy(&libs)); + StringSet min_libs_set = {}; + string_set_init(&min_libs_set, 64); + defer (string_set_destroy(&min_libs_set)); StringSet asm_files = {}; string_set_init(&asm_files, 64); @@ -149,6 +149,11 @@ gb_internal i32 linker_stage(LinkerData *gen) { for (Entity *e : gen->foreign_libraries) { GB_ASSERT(e->kind == Entity_LibraryName); + // NOTE(bill): Add these before the linking values + String extra_linker_flags = string_trim_whitespace(e->LibraryName.extra_linker_flags); + if (extra_linker_flags.len != 0) { + lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); + } for_array(i, e->LibraryName.paths) { String lib = string_trim_whitespace(e->LibraryName.paths[i]); // IMPORTANT NOTE(bill): calling `string_to_lower` here is not an issue because @@ -162,12 +167,11 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (!string_set_update(&asm_files, lib)) { String asm_file = asm_files.entries[i].value; String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); - String obj_format; -#if defined(GB_ARCH_64_BIT) - obj_format = str_lit("win64"); -#elif defined(GB_ARCH_32_BIT) + String obj_format = str_lit("win64"); + #if defined(GB_ARCH_32_BIT) obj_format = str_lit("win32"); -#endif // GB_ARCH_*_BIT + #endif + result = system_exec_command_line_app("nasm", "\"%.*s\\bin\\nasm\\windows\\nasm.exe\" \"%.*s\" " "-f \"%.*s\" " @@ -185,21 +189,13 @@ gb_internal i32 linker_stage(LinkerData *gen) { } array_add(&gen->output_object_paths, obj_file); } - } else { - if (!string_set_update(&libs, lib)) { - lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib)); - } + } else if (!string_set_update(&min_libs_set, lib) || + !build_context.min_link_libs) { + lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib)); } } } - for (Entity *e : gen->foreign_libraries) { - GB_ASSERT(e->kind == Entity_LibraryName); - if (e->LibraryName.extra_linker_flags.len != 0) { - lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags)); - } - } - if (build_context.build_mode == BuildMode_DynamicLibrary) { link_settings = gb_string_append_fmt(link_settings, " /DLL"); } else { @@ -318,12 +314,17 @@ gb_internal i32 linker_stage(LinkerData *gen) { string_set_init(&asm_files, 64); defer (string_set_destroy(&asm_files)); - StringSet libs = {}; - string_set_init(&libs, 64); - defer (string_set_destroy(&libs)); + StringSet min_libs_set = {}; + string_set_init(&min_libs_set, 64); + defer (string_set_destroy(&min_libs_set)); for (Entity *e : gen->foreign_libraries) { GB_ASSERT(e->kind == Entity_LibraryName); + // NOTE(bill): Add these before the linking values + String extra_linker_flags = string_trim_whitespace(e->LibraryName.extra_linker_flags); + if (extra_linker_flags.len != 0) { + lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); + } for (String lib : e->LibraryName.paths) { lib = string_trim_whitespace(lib); if (lib.len == 0) { @@ -336,19 +337,19 @@ gb_internal i32 linker_stage(LinkerData *gen) { String asm_file = lib; String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".o")); String obj_format; -#if defined(GB_ARCH_64_BIT) + #if defined(GB_ARCH_64_BIT) if (is_osx) { obj_format = str_lit("macho64"); } else { obj_format = str_lit("elf64"); } -#elif defined(GB_ARCH_32_BIT) + #elif defined(GB_ARCH_32_BIT) if (is_osx) { obj_format = str_lit("macho32"); } else { obj_format = str_lit("elf32"); } -#endif // GB_ARCH_*_BIT + #endif // GB_ARCH_*_BIT if (is_osx) { // `as` comes with MacOS. @@ -383,7 +384,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { } array_add(&gen->output_object_paths, obj_file); } else { - if (string_set_update(&libs, lib)) { + if (string_set_update(&min_libs_set, lib) && build_context.min_link_libs) { continue; } @@ -433,13 +434,6 @@ gb_internal i32 linker_stage(LinkerData *gen) { } } - for (Entity *e : gen->foreign_libraries) { - GB_ASSERT(e->kind == Entity_LibraryName); - if (e->LibraryName.extra_linker_flags.len != 0) { - lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags)); - } - } - gbString object_files = gb_string_make(heap_allocator(), ""); defer (gb_string_free(object_files)); for (String object_path : gen->output_object_paths) { diff --git a/src/main.cpp b/src/main.cpp index b8c21fd3b..2dbb72ca2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -296,6 +296,8 @@ enum BuildFlagKind { BuildFlag_ErrorPosStyle, BuildFlag_MaxErrorCount, + BuildFlag_MinLinkLibs, + // internal use only BuildFlag_InternalIgnoreLazy, BuildFlag_InternalIgnoreLLVMBuild, @@ -485,6 +487,8 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_ErrorPosStyle, str_lit("error-pos-style"), BuildFlagParam_String, Command_all); add_flag(&build_flags, BuildFlag_MaxErrorCount, str_lit("max-error-count"), BuildFlagParam_Integer, Command_all); + add_flag(&build_flags, BuildFlag_MinLinkLibs, str_lit("min-link-libs"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLLVMBuild, str_lit("internal-ignore-llvm-build"),BuildFlagParam_None, Command_all); @@ -1215,6 +1219,10 @@ gb_internal bool parse_build_flags(Array args) { break; } + case BuildFlag_MinLinkLibs: + build_context.min_link_libs = true; + break; + case BuildFlag_InternalIgnoreLazy: build_context.ignore_lazy = true; break; @@ -2008,6 +2016,11 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "If not set, the default max error count is %d.", DEFAULT_MAX_ERROR_COLLECTOR_COUNT); print_usage_line(0, ""); + print_usage_line(1, "-min-link-libs"); + print_usage_line(2, "If set, the number of linked libraries will be minimized to prevent duplications."); + print_usage_line(2, "This is useful for so called \"dumb\" linkers compared to \"smart\" linkers."); + print_usage_line(0, ""); + print_usage_line(1, "-foreign-error-procedures"); print_usage_line(2, "States that the error procedures used in the runtime are defined in a separate translation unit."); print_usage_line(0, ""); -- cgit v1.2.3 From 31407d9b1b8d0d35ba53a0245d86ed37007a28e2 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 4 Apr 2024 18:39:41 +0200 Subject: fix 128 bit int alignment on arm64 Fixes #2403 --- src/build_settings.cpp | 2 +- src/llvm_abi.cpp | 2 +- tests/internal/Makefile | 7 ++++-- tests/internal/build.bat | 4 ++- tests/internal/test_128.odin | 59 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 tests/internal/test_128.odin (limited to 'src/build_settings.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 1ac9e451f..a96c4491b 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -519,7 +519,7 @@ gb_global TargetMetrics target_darwin_amd64 = { gb_global TargetMetrics target_darwin_arm64 = { TargetOs_darwin, TargetArch_arm64, - 8, 8, 8, 16, + 8, 8, 16, 16, str_lit("arm64-apple-macosx"), // NOTE: Changes during initialization based on build flags. str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), }; diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 724e4e35a..507881f99 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -275,7 +275,7 @@ gb_internal i64 lb_alignof(LLVMTypeRef type) { case LLVMIntegerTypeKind: { unsigned w = LLVMGetIntTypeWidth(type); - return gb_clamp((w + 7)/8, 1, build_context.ptr_size); + return gb_clamp((w + 7)/8, 1, build_context.max_align); } case LLVMHalfTypeKind: return 2; diff --git a/tests/internal/Makefile b/tests/internal/Makefile index 00e46197b..daefd5959 100644 --- a/tests/internal/Makefile +++ b/tests/internal/Makefile @@ -1,6 +1,6 @@ ODIN=../../odin -all: rtti_test map_test pow_test +all: rtti_test map_test pow_test 128_test rtti_test: $(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal @@ -9,4 +9,7 @@ map_test: $(ODIN) run test_map.odin -file -vet -strict-style -o:minimal pow_test: - $(ODIN) run test_pow.odin -file -vet -strict-style -o:minimal \ No newline at end of file + $(ODIN) run test_pow.odin -file -vet -strict-style -o:minimal + +128_test: + $(ODIN) run test_128.odin -file -vet -strict-style -o:minimal diff --git a/tests/internal/build.bat b/tests/internal/build.bat index f289d17fa..da4fe890d 100644 --- a/tests/internal/build.bat +++ b/tests/internal/build.bat @@ -3,4 +3,6 @@ set PATH_TO_ODIN==..\..\odin rem %PATH_TO_ODIN% run test_rtti.odin -file -vet -strict-style -o:minimal || exit /b %PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal || exit /b rem -define:SEED=42 -%PATH_TO_ODIN% run test_pow.odin -file -vet -strict-style -o:minimal || exit /b \ No newline at end of file +%PATH_TO_ODIN% run test_pow.odin -file -vet -strict-style -o:minimal || exit /b + +%PATH_TO_ODIN% run test_128.odin -file -vet -strict-style -o:minimal || exit /b diff --git a/tests/internal/test_128.odin b/tests/internal/test_128.odin new file mode 100644 index 000000000..11ef068ed --- /dev/null +++ b/tests/internal/test_128.odin @@ -0,0 +1,59 @@ +package test_128 + +import "core:fmt" +import "core:os" +import "core:testing" + +TEST_count := 0 +TEST_fail := 0 + +when ODIN_TEST { + expect :: testing.expect + log :: testing.log +} else { + expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { + TEST_count += 1 + if !condition { + TEST_fail += 1 + fmt.printf("[%v] %v\n", loc, message) + return + } + } + log :: proc(t: ^testing.T, v: any, loc := #caller_location) { + fmt.printf("[%v] ", loc) + fmt.printf("log: %v\n", v) + } +} + +main :: proc() { + t := testing.T{} + + test_128_align(&t) + + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + if TEST_fail > 0 { + os.exit(1) + } +} + +@test +test_128_align :: proc(t: ^testing.T) { + Danger_Struct :: struct { + x: u128, + y: u64, + } + + list := [?]Danger_Struct{{0, 0}, {1, 0}, {2, 0}, {3, 0}} + + expect(t, list[0].x == 0, fmt.tprintf("[0].x (%v) != 0", list[0].x)) + expect(t, list[0].y == 0, fmt.tprintf("[0].y (%v) != 0", list[0].y)) + + expect(t, list[1].x == 1, fmt.tprintf("[1].x (%v) != 1", list[1].x)) + expect(t, list[1].y == 0, fmt.tprintf("[1].y (%v) != 0", list[1].y)) + + expect(t, list[2].x == 2, fmt.tprintf("[2].x (%v) != 2", list[2].x)) + expect(t, list[2].y == 0, fmt.tprintf("[2].y (%v) != 0", list[2].y)) + + expect(t, list[3].x == 3, fmt.tprintf("[3].x (%v) != 3", list[3].x)) + expect(t, list[3].y == 0, fmt.tprintf("[3].y (%v) != 0", list[3].y)) +} -- cgit v1.2.3